diff --git a/lib/guard/rspec/formatter.rb b/lib/guard/rspec/formatter.rb index 331d0040..4c79f802 100644 --- a/lib/guard/rspec/formatter.rb +++ b/lib/guard/rspec/formatter.rb @@ -6,10 +6,37 @@ class RSpec class Formatter < ::RSpec::Core::Formatters::BaseFormatter TEMPORARY_FILE_PATH = './tmp/rspec_guard_result' + # rspec issue https://github.com/rspec/rspec-core/issues/793 + def self.extract_spec_location(metadata) + root_metadata = metadata + location = metadata[:location] + + until spec_path?(location) + metadata = metadata[:example_group] + + if !metadata + Guard::UI.warning "no spec file found for #{root_metadata[:location]}" + return root_metadata[:location] + end + + location = (metadata[:location] || "").split(':').first # rspec issue https://github.com/rspec/rspec-core/issues/1243 + end + + location + end + + def self.spec_path?(path) + path ||= "" + flags = File::FNM_PATHNAME | File::FNM_DOTMATCH + if File.const_defined?(:FNM_EXTGLOB) # ruby >= 2 + flags |= File::FNM_EXTGLOB + end + File.fnmatch(::RSpec.configuration.pattern, path.sub(/:\d+\z/, ''), flags) + end + # Write summary to temporary file for runner def dump_summary(duration, total, failures, pending) - FileUtils.mkdir_p('tmp') - File.open(TEMPORARY_FILE_PATH, 'w') do |f| + write do |f| f.puts _message(total, failures, pending, duration) f.puts _failed_paths.join("\n") if failures > 0 end @@ -19,9 +46,14 @@ def dump_summary(duration, total, failures, pending) private + def write(&block) + FileUtils.mkdir_p('tmp') + File.open(TEMPORARY_FILE_PATH, 'w', &block) + end + def _failed_paths failed = examples.select { |e| e.execution_result[:status] == 'failed' } - failed.map { |e| e.metadata[:location] } + failed.map { |e| self.class.extract_spec_location(e.metadata) }.sort.uniq end def _message(example_count, failure_count, pending_count, duration) diff --git a/spec/lib/guard/rspec/formatter_spec.rb b/spec/lib/guard/rspec/formatter_spec.rb index e694ca93..6f023238 100644 --- a/spec/lib/guard/rspec/formatter_spec.rb +++ b/spec/lib/guard/rspec/formatter_spec.rb @@ -3,29 +3,76 @@ require 'guard/rspec/formatter' describe Guard::RSpec::Formatter do - let(:formatter) { Guard::RSpec::Formatter.new(StringIO.new) } + let(:writer){ + StringIO.new + } + let(:formatter) { + Guard::RSpec::Formatter.new(StringIO.new).tap{|formatter| + formatter.stub(:write) do |&block| + block.call writer + end + } + } describe '#dump_summary' do - after { File.delete('./tmp/rspec_guard_result') } + + let(:result){ + writer.rewind + writer.read + } + context 'with failures' do + let(:spec_filename){ + 'failed_location_spec.rb' + } + let(:failed_example) { double( execution_result: { status: 'failed' }, - metadata: { location: 'failed_location' } + metadata: { location: spec_filename } ) } it 'writes summary line and failed location in tmp dir' do allow(formatter).to receive(:examples) { [failed_example] } formatter.dump_summary(123, 3, 1, 0) - result = File.open('./tmp/rspec_guard_result').read - expect(result).to match /^3 examples, 1 failures in 123\.0 seconds\nfailed_location\n$/ + expect(result).to match /^3 examples, 1 failures in 123\.0 seconds\n#{spec_filename}\n$/ end + + it 'writes only uniq filenames out' do + allow(formatter).to receive(:examples) { [failed_example, failed_example] } + formatter.dump_summary(123, 3, 1, 0) + expect(result).to match /^3 examples, 1 failures in 123\.0 seconds\n#{spec_filename}\n$/ + end + + end + + it "should find the spec file for shared examples" do + metadata = {:location => './spec/support/breadcrumbs.rb:75', + :example_group => {:location => './spec/requests/breadcrumbs_spec.rb:218'} + } + + expect(described_class.extract_spec_location(metadata)).to start_with './spec/requests/breadcrumbs_spec.rb' + end + + it "should return only the spec file without line number for shared examples" do + metadata = {:location => './spec/support/breadcrumbs.rb:75', + :example_group => {:location => './spec/requests/breadcrumbs_spec.rb:218'} + } + + expect(described_class.extract_spec_location(metadata)).to eq './spec/requests/breadcrumbs_spec.rb' + end + + it "should return location of the root spec when a shared examples has no location" do + metadata = {:location => './spec/support/breadcrumbs.rb:75', + :example_group => {} + } + + expect(described_class.extract_spec_location(metadata)).to eq metadata[:location] end context 'with only success' do it 'notifies success' do formatter.dump_summary(123, 3, 0, 0) - result = File.open('./tmp/rspec_guard_result').read expect(result).to match /^3 examples, 0 failures in 123\.0 seconds\n$/ end end @@ -33,7 +80,6 @@ context 'with pending' do it "notifies pending too" do formatter.dump_summary(123, 3, 0, 1) - result = File.open('./tmp/rspec_guard_result').read expect(result).to match /^3 examples, 0 failures \(1 pending\) in 123\.0 seconds\n$/ end end