Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spec refactoring #8069

Merged
merged 4 commits into from May 31, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,7 @@
### New features

* [#6289](https://github.com/rubocop-hq/rubocop/issues/6289): Add new `CheckDefinitionPathHierarchy` option for `Naming/FileName`. ([@jschneid][])
* [#8069](https://github.com/rubocop-hq/rubocop/issues/8069): New option for `expect_offense` to help format offense templates. ([@marcandre][])

### Bug fixes

Expand Down
20 changes: 19 additions & 1 deletion lib/rubocop/rspec/expect_offense.rb
Expand Up @@ -71,9 +71,27 @@ module RSpec
# RUBY
#
# expect_no_corrections
#
# If your code has variables of different lengths, you can use `%{foo}`
# and `^{foo}` to format your template:
#
# %w[raise fail].each do |keyword|
# expect_offense(<<~RUBY, keyword: keyword)
# %{keyword}(RuntimeError, msg)
# ^{keyword}^^^^^^^^^^^^^^^^^^^ Redundant `RuntimeError` argument can be removed.
# RUBY
module ExpectOffense
def format_offense(source, **replacements)
replacements.each do |keyword, value|
source = source.gsub("%{#{keyword}}", value)
.gsub("^{#{keyword}}", '^' * value.size)
end
source
end

# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
def expect_offense(source, file = nil)
def expect_offense(source, file = nil, **replacements)
source = format_offense(source, **replacements)
RuboCop::Formatter::DisabledConfigFormatter
.config_to_allow_offenses = {}
RuboCop::Formatter::DisabledConfigFormatter.detected_styles = {}
Expand Down
84 changes: 38 additions & 46 deletions spec/rubocop/cli/cli_auto_gen_config_spec.rb
Expand Up @@ -758,53 +758,45 @@ def a; end
RUBY
expect(cli.run(['--auto-gen-config'])).to eq(0)
expect($stderr.string).to eq('')
expected =
['# This configuration was generated by',
'# `rubocop --auto-gen-config`',
/# on .* using RuboCop version .*/,
'# The point is for the user to remove these configuration records',
'# one by one as the offenses are removed from the code base.',
'# Note that changes in the inspected code, or installation of new',
'# versions of RuboCop, may require this file to be generated ' \
'again.',
'',
'# Offense count: 1',
'# Cop supports --auto-correct.',
'Layout/CommentIndentation:',
' Exclude:',
" - 'example2.rb'",
'',
'# Offense count: 1',
'# Cop supports --auto-correct.',
'# Configuration parameters: EnforcedStyle.',
'# SupportedStyles: normal, indented_internal_methods',
'Layout/IndentationConsistency:',
' Exclude:',
" - 'example2.rb'",
'',
'# Offense count: 1',
'# Cop supports --auto-correct.',
'# Configuration parameters: IndentationWidth, EnforcedStyle.',
'# SupportedStyles: spaces, tabs',
'Layout/IndentationStyle:',
' Exclude:',
" - 'example2.rb'",
'',
'# Offense count: 1',
'# Cop supports --auto-correct.',
'Layout/InitialIndentation:',
' Exclude:',
" - 'example2.rb'"]
actual = IO.read('.rubocop_todo.yml').split($RS)
expect(actual.length).to eq(expected.length)
expected.each_with_index do |line, ix|
if line.is_a?(String)
expect(actual[ix]).to eq(line)
else
expect(actual[ix]).to match(line)
end
end
expect(actual.size).to eq(expected.size)
date_stamp = actual.slice!(2)
expect(date_stamp).to match(/# on .* using RuboCop version .*/)
expect(actual.join("\n")).to eq(<<~TEXT.chomp)
# This configuration was generated by
# `rubocop --auto-gen-config`
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.

# Offense count: 1
# Cop supports --auto-correct.
Layout/CommentIndentation:
Exclude:
- 'example2.rb'

# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: normal, indented_internal_methods
Layout/IndentationConsistency:
Exclude:
- 'example2.rb'

# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: IndentationWidth, EnforcedStyle.
# SupportedStyles: spaces, tabs
Layout/IndentationStyle:
Exclude:
- 'example2.rb'

# Offense count: 1
# Cop supports --auto-correct.
Layout/InitialIndentation:
Exclude:
- 'example2.rb'
TEXT
end

it 'generates a todo list that removes the reports' do
Expand Down
4 changes: 3 additions & 1 deletion spec/rubocop/cop/cop_spec.rb
Expand Up @@ -171,7 +171,9 @@
context 'when offense was corrected' do
before do
allow(cop).to receive(:autocorrect?).and_return(true)
allow(cop).to receive(:autocorrect).and_return(->(_corrector) {})
allow(cop).to receive(:autocorrect).and_return(lambda do |corrector|
corrector.insert_before(location, 'hi!')
end)
end

it 'is set to true' do
Expand Down
38 changes: 12 additions & 26 deletions spec/rubocop/cop/corrector_spec.rb
Expand Up @@ -12,13 +12,13 @@
let(:operator) { node.loc.operator }

def do_rewrite(corrections = nil, &block)
corrections = Array(corrections || block).map do |c|
node = instance_double(RuboCop::AST::Node)
cop = instance_double(RuboCop::Cop::Cop)
RuboCop::Cop::Cop::Correction.new(c, node, cop)
corrector = described_class.new(processed_source.buffer)

Array(corrections || block).each do |c|
c.call(corrector)
end

described_class.new(processed_source.buffer, corrections).rewrite
corrector.rewrite
end

matcher :rewrite_to do |expected|
Expand Down Expand Up @@ -86,16 +86,10 @@ def do_rewrite(corrections = nil, &block)
end

it 'raises a useful error if not given a node or a range' do
# rubocop:disable Style/MultilineBlockChain
expect do
do_rewrite { |corr| corr.replace(1..3, 'oops') }
end.to raise_error(RuboCop::ErrorWithAnalyzedFileLocation) do |e|
expect(e.cause.message).to eq(
'Expected a Parser::Source::Range, Comment or Rubocop::AST::Node, ' \
'got Range'
)
end
# rubocop:enable Style/MultilineBlockChain
end.to raise_error(TypeError, 'Expected a Parser::Source::Range, '\
'Comment or Rubocop::AST::Node, got Range')
end

context 'when range is from incorrect source' do
Expand All @@ -117,23 +111,15 @@ def do_rewrite(corrections = nil, &block)
remove_trailing: [2]
}.each_pair do |method, params|
it "raises exception from #{method}" do
# rubocop:disable Style/MultilineBlockChain
expect do
do_rewrite { |corr| corr.public_send(method, op_string, *params) }
end.to raise_error(RuboCop::ErrorWithAnalyzedFileLocation) do |e|
expect(e.cause.message).to eq(
'Corrector expected range source buffer to be'\
' a Parser::Source::Buffer, but got String'
)
end
end.to raise_error(RuntimeError,
'Corrector expected range source buffer to be'\
' a Parser::Source::Buffer, but got String')
expect do
do_rewrite { |corr| corr.public_send(method, op_other, *params) }
end.to raise_error(RuboCop::ErrorWithAnalyzedFileLocation) do |e|
expect(e.cause.message).to match(
/^Correction target buffer \d+ name:"\(string\)" is not current/
)
end
# rubocop:enable Style/MultilineBlockChain
end.to raise_error(RuntimeError,
/^Correction target buffer \d+ name:"\(string\)" is not current/)
end
end
end
Expand Down
84 changes: 36 additions & 48 deletions spec/rubocop/cop/style/redundant_exception_spec.rb
Expand Up @@ -5,67 +5,55 @@

shared_examples 'common behavior' do |keyword|
it "reports an offense for a #{keyword} with RuntimeError" do
src = "#{keyword} RuntimeError, msg"
inspect_source(src)
expect(cop.highlights).to eq([src])
expect(cop.messages)
.to eq(['Redundant `RuntimeError` argument can be removed.'])
end
expect_offense(<<~RUBY, keyword: keyword)
%{keyword} RuntimeError, msg
^{keyword}^^^^^^^^^^^^^^^^^^ Redundant `RuntimeError` argument can be removed.
RUBY

it "reports an offense for a #{keyword} with RuntimeError.new" do
src = "#{keyword} RuntimeError.new(msg)"
inspect_source(src)
expect(cop.highlights).to eq([src])
expect(cop.messages)
.to eq(['Redundant `RuntimeError.new` call can be replaced with ' \
'just the message.'])
expect_correction(<<~RUBY)
#{keyword} msg
RUBY
end

it "accepts a #{keyword} with RuntimeError if it does not have 2 args" do
expect_no_offenses("#{keyword} RuntimeError, msg, caller")
end
it "reports an offense for a #{keyword} with RuntimeError and ()" do
expect_offense(<<~RUBY, keyword: keyword)
%{keyword}(RuntimeError, msg)
^{keyword}^^^^^^^^^^^^^^^^^^^ Redundant `RuntimeError` argument can be removed.
RUBY

it "auto-corrects a #{keyword} RuntimeError by removing RuntimeError" do
src = "#{keyword} RuntimeError, msg"
result_src = "#{keyword} msg"
new_src = autocorrect_source(src)
expect(new_src).to eq(result_src)
expect_correction(<<~RUBY)
#{keyword}(msg)
RUBY
end

it "auto-corrects a #{keyword} RuntimeError and leaves parentheses" do
src = "#{keyword}(RuntimeError, msg)"
result_src = "#{keyword}(msg)"
new_src = autocorrect_source(src)
expect(new_src).to eq(result_src)
end
it "reports an offense for a #{keyword} with RuntimeError.new" do
expect_offense(<<~RUBY, keyword: keyword)
%{keyword} RuntimeError.new msg
^{keyword}^^^^^^^^^^^^^^^^^^^^^ Redundant `RuntimeError.new` call can be replaced with just the message.
RUBY

it "auto-corrects a #{keyword} RuntimeError.new with parentheses by " \
'removing RuntimeError.new' do
src = "#{keyword} RuntimeError.new(msg)"
result_src = "#{keyword} msg"
new_src = autocorrect_source(src)
expect(new_src).to eq(result_src)
expect_correction(<<~RUBY)
#{keyword} msg
RUBY
end

it "auto-corrects a #{keyword} RuntimeError.new without parentheses by " \
'removing RuntimeError.new' do
src = "#{keyword} RuntimeError.new msg"
result_src = "#{keyword} msg"
new_src = autocorrect_source(src)
expect(new_src).to eq(result_src)
it "reports an offense for a #{keyword} with RuntimeError.new" do
expect_offense(<<~RUBY, keyword: keyword)
%{keyword} RuntimeError.new(msg)
^{keyword}^^^^^^^^^^^^^^^^^^^^^^ Redundant `RuntimeError.new` call can be replaced with just the message.
RUBY

expect_correction(<<~RUBY)
#{keyword} msg
RUBY
end

it "does not modify #{keyword} w/ RuntimeError if it does not have 2 " \
'args' do
src = "#{keyword} runtimeError, msg, caller"
new_src = autocorrect_source(src)
expect(new_src).to eq(src)
it "accepts a #{keyword} with RuntimeError if it does not have 2 args" do
expect_no_offenses("#{keyword} RuntimeError, msg, caller")
end

it 'does not modify rescue w/ non redundant error' do
src = "#{keyword} OtherError, msg"
new_src = autocorrect_source(src)
expect(new_src).to eq(src)
it 'accepts rescue w/ non redundant error' do
expect_no_offenses "#{keyword} OtherError, msg"
end
end

Expand Down
37 changes: 0 additions & 37 deletions spec/rubocop/cop/team_spec.rb
Expand Up @@ -210,43 +210,6 @@ def a
expect($stderr.string).to include(error_message)
end
end

context 'when a correction that receives Parser::Source::Range raises ' \
'an error' do
include_context 'mock console output'

before do
allow_any_instance_of(RuboCop::Cop::Layout::IndentationStyle)
.to receive(:autocorrect).and_return(buggy_correction)

create_file(file_path, <<~RUBY)
def foo
\tbar
end
RUBY
end

let(:buggy_correction) do
lambda do |_corrector|
raise cause
end
end
let(:options) { { auto_correct: true } }

let(:cause) { StandardError.new('cause') }

let(:error_message) do
'An error occurred while Layout/IndentationStyle cop was inspecting ' \
'/tmp/example.rb:2:0.'
end

it 'records Team#errors' do
source = RuboCop::ProcessedSource.from_file(file_path, ruby_version)

expect { team.inspect_file(source) }.to raise_error(cause)
expect($stderr.string).to include(error_message)
end
end
end

describe '#cops' do
Expand Down
7 changes: 2 additions & 5 deletions spec/rubocop/formatter/emacs_style_formatter_spec.rb
@@ -1,16 +1,13 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Formatter::EmacsStyleFormatter do
RSpec.describe RuboCop::Formatter::EmacsStyleFormatter, :config do
subject(:formatter) { described_class.new(output) }

let(:source) { %w[a b cdefghi].join("\n") }
let(:output) { StringIO.new }

describe '#file_finished' do
it 'displays parsable text' do
cop = RuboCop::Cop::Cop.new
source_buffer = Parser::Source::Buffer.new('test', 1)
source_buffer.source = %w[a b cdefghi].join("\n")

cop.add_offense(
nil,
location: Parser::Source::Range.new(source_buffer, 0, 1),
Expand Down
8 changes: 3 additions & 5 deletions spec/rubocop/formatter/file_list_formatter_spec.rb
@@ -1,16 +1,14 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Formatter::FileListFormatter do
RSpec.describe RuboCop::Formatter::FileListFormatter, :config do
subject(:formatter) { described_class.new(output) }

let(:output) { StringIO.new }

let(:source) { %w[a b cdefghi].join("\n") }

describe '#file_finished' do
it 'displays parsable text' do
cop = RuboCop::Cop::Cop.new
source_buffer = Parser::Source::Buffer.new('test', 1)
source_buffer.source = %w[a b cdefghi].join("\n")

cop.add_offense(
nil,
location: Parser::Source::Range.new(source_buffer, 0, 1),
Expand Down