Skip to content

Commit

Permalink
Merge pull request #2 from appbot/add-config-class
Browse files Browse the repository at this point in the history
Add rspec/rails integration
  • Loading branch information
gondalez authored Aug 21, 2024
2 parents a5a23df + 1579480 commit 84abebc
Show file tree
Hide file tree
Showing 10 changed files with 270 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ AllCops:
Exclude:
- 'bin/*'
- 'vendor/bundle/**/*'
- 'spec/fixtures/**/*'
NewCops: enable
SuggestExtensions: false

Expand Down Expand Up @@ -142,3 +143,4 @@ RSpec/SpecFilePathFormat:
RuboCop: rubocop
RSpec: rspec
SimpleCov: simplecov
RSpecFormatterSkipOnFailure: rspec_formatter_skip_on_failure
2 changes: 2 additions & 0 deletions lib/simplecov/inline.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
require_relative 'inline/version'
require_relative 'inline/formatter'
require_relative 'inline/rspec_formatter_skip_on_failure'
require_relative 'inline/integration'

module SimpleCov
module Inline
Expand Down
2 changes: 1 addition & 1 deletion lib/simplecov/inline/formatter.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require 'rainbow'

module Simplecov
module SimpleCov
module Inline
class Formatter
Result = Struct.new(:file, :start_line, :end_line, :type) do
Expand Down
61 changes: 61 additions & 0 deletions lib/simplecov/inline/integration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
module SimpleCov
module Inline
class Integration
class << self
def configure_rspec_rails(rspec: RSpec, rails: Rails)
rspec.configure do |rspec_config|
rspec_config.add_formatter SimpleCov::Inline::RSpecFormatterSkipOnFailure
rspec_config.before(:suite) do
SimpleCov::Inline::Integration.configure_formatter(rspec_config:, rails_root: rails.root.to_s)
end
end
end

def configure_formatter(rspec_config:, rails_root:)
# Restrict coverage reporting to spec files and the file they are testing.
# Note files_to_run contains all spec files when running rspec with no args,
# so in that case do not filter.
# To do so would exclude files that are covered but not directly tested.
return if running_all_specs?(rspec_config:, rails_root:)

SimpleCov::Inline::Formatter.config do |coverage_config|
if rspec_config.inclusion_filter.rules.key?(:locations)
# Skip coverage output if running rspec against a single line.
# e.g. spec/x_spec.rb:123
coverage_config.no_output!(reason: 'filtered to line of code')
else
coverage_config.files = rspec_config.files_to_run.flat_map do |spec_path|
[spec_path, rails_path_under_test(spec_path:, rails_root:)]
end.compact
end
end
end

private

def running_all_specs?(rspec_config:, rails_root:)
Dir.glob("#{rails_root}#{rspec_config.pattern}").to_set == rspec_config.files_to_run.to_set
end

def rails_path_under_test(spec_path:, rails_root:)
# Mappings
# /app/spec/lib/x_spec.rb -> /app/lib/x.rb
# /app/spec/controller/y_spec.rb -> /app/app/controllers/y.rb
raise 'Spec file must be in rails root.' unless spec_path.start_with?(rails_root)

_spec_root, spec_type, *other_directories, filename = spec_path[(rails_root.length + 1)..].split('/')

return if filename.nil?

filename_under_test = filename.gsub(/_spec.rb$/, '.rb')

if spec_type == 'lib'
[rails_root, spec_type, *other_directories, filename_under_test]
else
[rails_root, 'app', spec_type, *other_directories, filename_under_test]
end.compact.join('/')
end
end
end
end
end
28 changes: 28 additions & 0 deletions lib/simplecov/inline/rspec_formatter_skip_on_failure.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module SimpleCov
module Inline
class RSpecFormatterSkipOnFailure
RSpec::Core::Formatters.register self, :dump_failures

def initialize(output)
@output = output
end

def dump_failures(notification)
return unless skip_reason(notification:)

SimpleCov::Inline::Formatter.config do |coverage_config|
coverage_config.no_output!(reason: skip_reason(notification:))
end
end

private

def skip_reason(notification:)
return 'no examples were run' if notification.examples.none?
return 'some specs failed' if notification.failed_examples.any?

nil
end
end
end
end
Empty file.
Empty file.
2 changes: 1 addition & 1 deletion spec/lib/simplecov/inline/formatter_spec.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
RSpec.describe Simplecov::Inline::Formatter do
RSpec.describe SimpleCov::Inline::Formatter do
after { described_class.reset_config }

describe '#format' do
Expand Down
131 changes: 131 additions & 0 deletions spec/lib/simplecov/inline/integration_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
RSpec.describe SimpleCov::Inline::Integration do
after { SimpleCov::Inline::Formatter.reset_config }

describe '#configure_rspec_rails' do
subject { described_class.configure_rspec_rails(rspec:, rails:) }

let(:rspec) { class_double(RSpec) }
let(:rspec_config) { instance_double(RSpec::Core::Configuration) }

let(:rails) { double('Rails', root: :fake_root) } # rubocop:todo RSpec/VerifiedDoubles

before do
allow(rspec).to receive(:configure).and_yield(rspec_config)
allow(rspec_config).to receive(:add_formatter)
allow(rspec_config).to receive(:before)
allow(described_class).to receive(:configure_formatter)
end

it 'adds a formatter to the rspec config', :aggregate_failures do
subject

expect(rspec_config).to have_received(:add_formatter).with(SimpleCov::Inline::RSpecFormatterSkipOnFailure)
end

it 'adds a before suite hook that calls configure_formatter', :aggregate_failures do
subject

expect(rspec_config).to have_received(:before).with(:suite) do |&block|
block.call
end

expect(described_class).to have_received(:configure_formatter).with(rspec_config:, rails_root: 'fake_root')
end
end

describe '#configure_formatter' do
subject { described_class.configure_formatter(rspec_config:, rails_root:) }

let(:rspec_config) { instance_double(RSpec::Core::Configuration, pattern:, files_to_run:) }
let(:rails_root) { "#{fixture_directory}/fake_rails_root" }
let(:files_to_run) { ["#{rails_root}/spec/lib/lib_spec.rb", "#{rails_root}/spec/models/model_spec.rb"] }
let(:pattern) { '**{,/*/**}/*_spec.rb' }
let(:inclusion_rules) { instance_double(RSpec::Core::InclusionRules, rules: {}) } # no rules means run all

before { allow(rspec_config).to receive(:inclusion_filter).and_return(inclusion_rules) }

context 'running all specs' do
let(:pattern) { '**{,/*/**}/*_spec.rb' }

it 'does not filter files' do
expect { subject }.not_to change { SimpleCov::Inline::Formatter.config.files }.from(nil)
end

it 'does not supress output' do
expect { subject }.not_to change { SimpleCov::Inline::Formatter.config.no_output }.from(nil)
end
end

context 'running only a lib spec' do
let(:files_to_run) { ["#{rails_root}/spec/lib/lib_spec.rb"] }

it 'does not supress output' do
expect { subject }.not_to change { SimpleCov::Inline::Formatter.config.no_output }.from(nil)
end

it 'filters by the spec file and the file it is testing' do
expect { subject }
.to change { SimpleCov::Inline::Formatter.config.files }
.from(nil).to(["#{rails_root}/spec/lib/lib_spec.rb", "#{rails_root}/lib/lib.rb"])
end
end

context 'running only a model spec' do
let(:files_to_run) { ["#{rails_root}/spec/models/model_spec.rb"] }

it 'does not supress output' do
expect { subject }.not_to change { SimpleCov::Inline::Formatter.config.no_output }.from(nil)
end

it 'filters by the spec file and the file it is testing' do
expect { subject }
.to change { SimpleCov::Inline::Formatter.config.files }
.from(nil).to(["#{rails_root}/spec/models/model_spec.rb", "#{rails_root}/app/models/model.rb"])
end
end

context 'filtered to a line of code' do
let(:files_to_run) { ["#{rails_root}/spec/models/model_spec.rb"] }
let(:inclusion_rules) do
instance_double(
RSpec::Core::InclusionRules,
rules: {focus: true, locations: {"#{rails_root}/spec/models/model_spec.rb" => [5]}}, # 5 is the line number
)
end

it 'supresses output' do
expect { subject }
.to change { SimpleCov::Inline::Formatter.config.no_output }
.from(nil)
.to('filtered to line of code')
end

it 'does not filter files' do
expect { subject }.not_to change { SimpleCov::Inline::Formatter.config.files }.from(nil)
end
end

context 'bad spec path' do
let(:files_to_run) { ['/not/rails/root/spec/models/model_spec.rb'] }

it 'filters by the spec file and the file it is testing' do
expect { subject }.to raise_error RuntimeError, 'Spec file must be in rails root.'
end
end

context 'directory provided' do
let(:files_to_run) { ["#{rails_root}/spec/models"] }

it 'does not supress output' do
expect { subject }.not_to change { SimpleCov::Inline::Formatter.config.no_output }.from(nil)
end

it 'only filters to the spec directory and does inlcude the covered files' do
# Existing behaviour. Feels like it should suppress instead.
expect { subject }
.to change { SimpleCov::Inline::Formatter.config.files }
.from(nil).to(["#{rails_root}/spec/models"])
end
end
end
end
44 changes: 44 additions & 0 deletions spec/lib/simplecov/inline/rspec_formatter_skip_on_failure_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
RSpec.describe SimpleCov::Inline::RSpecFormatterSkipOnFailure do
after { SimpleCov::Inline::Formatter.reset_config }

describe '#dump_failures' do
subject { described_class.new(:output_arg_value_not_used).dump_failures(notification) }

let(:notification) do
instance_double(RSpec::Core::Notifications::ExamplesNotification, examples:, failed_examples:)
end

context 'no specs ran' do
let(:examples) { [] }
let(:failed_examples) { [] }

it 'supresses output' do
expect { subject }
.to change { SimpleCov::Inline::Formatter.config.no_output }
.from(nil)
.to('no examples were run')
end
end

context 'specs ran but there were failures' do
let(:examples) { ['a_spec.rb', 'b_spec.rb'] }
let(:failed_examples) { ['a_spec.rb'] }

it 'supresses output' do
expect { subject }
.to change { SimpleCov::Inline::Formatter.config.no_output }
.from(nil)
.to('some specs failed')
end
end

context 'some specs ran without error' do
let(:examples) { ['a_spec.rb', 'b_spec.rb'] }
let(:failed_examples) { [] }

it 'does not supress output' do
expect { subject }.not_to change { SimpleCov::Inline::Formatter.config.no_output }.from(nil)
end
end
end
end

0 comments on commit 84abebc

Please sign in to comment.