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

Add better inspect output for ExampleGroup #1687

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
11 changes: 10 additions & 1 deletion lib/rspec/core/example.rb
Expand Up @@ -82,6 +82,15 @@ def description
RSpec.configuration.format_docstrings_block.call(description)
end

# Returns a description of the example that always includes the location.
def inspect_output
inspect_output = "\"#{description}\""
unless metadata[:description].to_s.empty?
inspect_output << " (#{location})"
end
inspect_output
end

# @attr_reader
#
# Returns the first exception raised in the context of running this
Expand Down Expand Up @@ -170,7 +179,7 @@ def run(example_group_instance, reporter)
rescue Exception => e
set_exception(e)
ensure
@example_group_instance.instance_variables.each do |ivar|
ExampleGroup.instance_variables_for_example(@example_group_instance).each do |ivar|
@example_group_instance.instance_variable_set(ivar, nil)
end
@example_group_instance = nil
Expand Down
33 changes: 27 additions & 6 deletions lib/rspec/core/example_group.rb
Expand Up @@ -419,9 +419,7 @@ def self.before_context_ivars

# @private
def self.store_before_context_ivars(example_group_instance)
return if example_group_instance.instance_variables.empty?

example_group_instance.instance_variables.each do |ivar|
instance_variables_for_example(example_group_instance).each do |ivar|
before_context_ivars[ivar] = example_group_instance.instance_variable_get(ivar)
end
end
Expand Down Expand Up @@ -459,7 +457,8 @@ def self.run(reporter)
reporter.example_group_started(self)

begin
run_before_context_hooks(new)
instance = new('before(:context) hook')
run_before_context_hooks(instance)
result_for_this_group = run_examples(reporter)
results_for_descendants = ordering_strategy.order(children).map { |child| child.run(reporter) }.all?
result_for_this_group && results_for_descendants
Expand All @@ -469,7 +468,8 @@ def self.run(reporter)
RSpec.world.wants_to_quit = true if fail_fast?
for_filtered_examples(reporter) { |example| example.fail_with_exception(reporter, ex) }
ensure
run_after_context_hooks(new)
instance = new('after(:context) hook')
run_after_context_hooks(instance)
before_context_ivars.clear
reporter.example_group_finished(self)
end
Expand All @@ -495,7 +495,7 @@ def self.ordering_strategy
def self.run_examples(reporter)
ordering_strategy.order(filtered_examples).map do |example|
next if RSpec.world.wants_to_quit
instance = new
instance = new(example.inspect_output)
set_ivars(instance, before_context_ivars)
succeeded = example.run(instance, reporter)
RSpec.world.wants_to_quit = true if fail_fast? && !succeeded
Expand Down Expand Up @@ -570,6 +570,27 @@ def self.pending_metadata_and_block_for(options, block)

return options, callback
end

if RUBY_VERSION.to_f < 1.9
# @private
def self.instance_variables_for_example(group)
group.instance_variables - ['@__inspect_output']
end
else
# @private
def self.instance_variables_for_example(group)
group.instance_variables - [:@__inspect_output]
end
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For ruby version differences we tend to prefer to define differing versions of methods to make it explicit what environments need which logic and to only incur the overhead at file load time rather than each time the method is called. In this case, it could be:

if RUBY_VERSION.to_f < 1.9
  if self.instance_variables_for_example(group)
    group.instance_variables - ['@__inspect_output']
  end
else
  if self.instance_variables_for_example(group)
    group.instance_variables - [:@__inspect_output]
  end
end


def initialize(inspect_output=nil)
@__inspect_output = inspect_output || '(no description provided)'
end

# @private
def inspect
"#<#{self.class} #{@__inspect_output}>"
end
end

# @private
Expand Down
92 changes: 92 additions & 0 deletions spec/rspec/core/example_group_spec.rb
Expand Up @@ -1604,5 +1604,97 @@ def foo; end
end
}.to raise_error(/not allowed/)
end

describe 'inspect output' do
context 'when there is no inspect output provided' do
it "uses '(no description provided)' instead" do
expect(ExampleGroup.new.inspect).to eq('#<RSpec::Core::ExampleGroup (no description provided)>')
end
end

context 'when an example has a description' do
it 'includes description and location' do
an_example = nil

line = __LINE__ + 2
group = ExampleGroup.describe 'SomeClass1' do
example 'an example' do
an_example = self
end
end

group.run

path = RSpec::Core::Metadata.relative_path(__FILE__)
expect(an_example.inspect).to eq("#<RSpec::ExampleGroups::SomeClass1 \"an example\" (#{path}:#{line})>")
end
end

context 'when an example does not have a description' do
it 'includes fallback description' do
an_example = nil

line = __LINE__ + 2
group = ExampleGroup.describe 'SomeClass2' do
example do
an_example = self
end
end

group.run

path = RSpec::Core::Metadata.relative_path(__FILE__)
expect(an_example.inspect).to eq("#<RSpec::ExampleGroups::SomeClass2 \"example at #{path}:#{line}\">")
end
end

it 'handles before context hooks' do
a_before_hook = nil

group = ExampleGroup.describe 'SomeClass3' do
before(:context) do
a_before_hook = self
end

example {}
end

group.run
expect(a_before_hook.inspect).to eq("#<RSpec::ExampleGroups::SomeClass3 before(:context) hook>")
end

it 'handles after context hooks' do
an_after_hook = nil

group = ExampleGroup.describe 'SomeClass4' do
after(:context) do
an_after_hook = self
end

example {}
end

group.run
expect(an_after_hook.inspect).to eq("#<RSpec::ExampleGroups::SomeClass4 after(:context) hook>")
end

it "does not pollute an example's `inspect` output with the inspect ivar from `before(:context)`" do
inspect_output = nil

line = __LINE__ + 2
group = ExampleGroup.describe do
example do
inspect_output = inspect
end

before(:context) {}
end

group.run

path = RSpec::Core::Metadata.relative_path(__FILE__)
expect(inspect_output).to end_with("\"example at #{path}:#{line}\">")
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accessing the instance variable directly like this is a code smell, IMO. I think this spec can be tweaked slightly to retain it's essence without doing that:

      it 'does not clear the inspect output after the example runs' do
        an_example = nil

        line = __LINE__ + 2
        group = ExampleGroup.describe do
          example do
            an_example = self
          end

          before(:context) {}
        end

        group.run

        path = RSpec::Core::Metadata.relative_path(__FILE__)
        expect(an_example.inspect).to end_with("\"example at #{path}:#{line}\">")
      end

Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm going to get rid of this spec. It's very similar to:

        it 'includes description and location' do
          an_example = nil

          line = __LINE__ + 2
          group = ExampleGroup.describe 'SomeClass1' do
            example 'an example' do
              an_example = self
            end
          end

          group.run

          path = RSpec::Core::Metadata.relative_path(__FILE__)
          expect(an_example.inspect).to eq("#<RSpec::ExampleGroups::SomeClass1 \"an example\" (#{path}:#{line})>")
        end

end
end
end