<NME> reporter_spec.rb
<BEF> module RSpec::Core
  RSpec.describe Reporter do
    include FormatterSupport

    let(:config)   { Configuration.new }
    let(:world)    { World.new(config) }
    let(:reporter) { Reporter.new config }
    let(:start_time) { Time.now }
    let(:example) { super() }

    describe "finish" do
      let(:formatter) { double("formatter") }

      %w[start_dump dump_pending dump_failures dump_summary close].map(&:to_sym).each do |message|
        it "sends #{message} to the formatter(s) that respond to message" do
          reporter.register_listener formatter, message
          expect(formatter.as_null_object).to receive(message)
          reporter.finish
        end

        it "doesnt notify formatters about messages they dont implement" do
          expect { reporter.finish }.to_not raise_error
        end
      end

      it "dumps the failure summary after the profile and deprecation summary so failures don't scroll off the screen and get missed" do
        config.profile_examples = 10
        formatter = instance_double("RSpec::Core::Formatter::ProgressFormatter")
        reporter.register_listener(formatter, :dump_summary, :dump_profile, :deprecation_summary)

        expect(formatter).to receive(:deprecation_summary).ordered
        expect(formatter).to receive(:dump_profile).ordered
        expect(formatter).to receive(:dump_summary).ordered

        reporter.finish
      end

      it "allows the profiler to be used without being manually setup" do
        config.profile_examples = true
        expect {
          reporter.finish
        }.to_not raise_error
      end
    end

    describe 'start' do
      before { config.start_time = start_time }

      it 'notifies the formatter of start with example count' do
        formatter = double("formatter")
        reporter.register_listener formatter, :start

        expect(formatter).to receive(:start) do |notification|
          expect(notification.count).to eq 3
          expect(notification.load_time).to eq 5
        end

        reporter.start 3, (start_time + 5)
      end

      it 'notifies the formatter of the seed used before notifing of start' do
        formatter = double("formatter")
        reporter.register_listener formatter, :seed
        reporter.register_listener formatter, :start
        expect(formatter).to receive(:seed).ordered.with(
          an_object_having_attributes(:seed => config.seed, :seed_used? => config.seed_used?)
        )
        expect(formatter).to receive(:start).ordered
        reporter.start 1
      end
    end

    context "given one formatter" do
      it "passes messages to that formatter" do
        formatter = double("formatter", :example_started => nil)
        reporter.register_listener formatter, :example_started
        example = new_example

        expect(formatter).to receive(:example_started) do |notification|
          expect(notification.example).to eq example
        end
        reporter.example_started(example)
      end
    end
  end
end
          example("passing example") {}
          example("pending example", :skip => true) { }
        end
        group.describe("context 2") do
          example("failed example") { fail }
        end

        group.run(reporter)

        expect(order).to eq([
           "Started: root",
           "Started: context 1",
           "Started Example",
           "Finished Example",
           "Passed",
           "Started Example",
           "Finished Example",
           "Pending",
           "Finished: context 1",
           "Started: context 2",
           "Started Example",
           "Finished Example",
           "Failed",
           "Finished: context 2",
           "Finished: root"
        ])
      end
    end

    context "given an example group with no examples" do
      it "does not pass example_group_started or example_group_finished to formatter" do
        formatter = double("formatter")
        expect(formatter).not_to receive(:example_group_started)
        expect(formatter).not_to receive(:example_group_finished)

        reporter.register_listener formatter, :example_group_started, :example_group_finished

        group = RSpec.describe("root")

        group.run(reporter)
      end
    end

    context "given multiple formatters" do
      it "passes messages to all formatters" do
        formatters = (1..2).map { double("formatter", :example_started => nil) }
        example = new_example

        formatters.each do |formatter|
          expect(formatter).to receive(:example_started) do |notification|
            expect(notification.example).to eq example
          end
          reporter.register_listener formatter, :example_started
        end

        reporter.example_started(example)
      end
    end

    describe "#exit_early" do
      it "returns the passed exit code" do
        expect(reporter.exit_early(42)).to eq(42)
      end

      it "sends a complete cycle of notifications" do
        formatter = double("formatter")
        %w[seed start start_dump dump_pending dump_failures dump_summary seed close].map(&:to_sym).each do |message|
          reporter.register_listener formatter, message
          expect(formatter).to receive(message).ordered
        end
        reporter.exit_early(42)
      end
    end

    describe "#report" do
      it "supports one arg (count)" do
        reporter.report(1) {}
      end

      it "yields itself" do
        yielded = nil
        reporter.report(3) { |r| yielded = r }
        expect(yielded).to eq(reporter)
      end
    end

    describe "#register_listener" do
      let(:listener) { double("listener", :start => nil) }

      before { reporter.register_listener listener, :start }

      it 'will register the listener to specified notifications' do
        expect(reporter.registered_listeners :start).to eq [listener]
      end

      it 'will match string notification names' do
        reporter.register_listener listener, "stop"
        expect(reporter.registered_listeners :stop).to eq [listener]
      end

      it 'will send notifications when a subscribed event is triggered' do
        expect(listener).to receive(:start) do |notification|
          expect(notification.count).to eq 42
        end
        reporter.start 42
      end

      it 'will ignore duplicated listeners' do
        reporter.register_listener listener, :start
        expect(listener).to receive(:start).once
        reporter.start 42
      end
    end

    describe "#publish" do
      let(:listener) { double("listener", :custom => nil) }
      before do
        reporter.register_listener listener, :custom, :start
      end

      it 'will send custom events to registered listeners' do
        expect(listener).to receive(:custom).with(RSpec::Core::Notifications::NullNotification)
        reporter.publish :custom
      end

      it 'will raise when encountering RSpec standard events' do
        expect { reporter.publish :start }.to raise_error(
          StandardError,
          a_string_including("not internal RSpec ones")
        )
      end

      it 'will ignore event names sent as strings' do
        expect(listener).not_to receive(:custom)
        reporter.publish "custom"
      end

      it 'will provide a custom notification object based on the options hash' do
        expect(listener).to receive(:custom).with(
          an_object_having_attributes(:my_data => :value)
        )
        reporter.publish :custom, :my_data => :value
      end
    end

    describe "#abort_with" do
      before { allow(reporter).to receive(:exit!) }

      it "publishes the message and notifies :close" do
        listener = double("Listener")
        reporter.register_listener(listener, :message, :close)
        stream   = StringIO.new

        allow(listener).to receive(:message) { |n| stream << n.message }
        allow(listener).to receive(:close) { stream.close }

        reporter.register_listener(listener)
        reporter.abort_with("Booom!", 1)

        expect(stream).to have_attributes(:string => "Booom!").and be_closed
      end

      it "exits with the provided exit code" do
        reporter.abort_with("msg", 13)
        expect(reporter).to have_received(:exit!).with(13)
      end
    end

    describe "timing" do
      before do
        config.start_time = start_time
      end

      it "uses RSpec::Core::Time as to not be affected by changes to time in examples" do
        formatter = double(:formatter)
        reporter.register_listener formatter, :dump_summary
        reporter.start 1
        allow(Time).to receive_messages(:now => ::Time.utc(2012, 10, 1))

        duration = nil
        allow(formatter).to receive(:dump_summary) do |notification|
          duration = notification.duration
        end

        reporter.finish
        expect(duration).to be < 0.2
      end

      it "captures the load time so it can report it later" do
        formatter = instance_double("ProgressFormatter")
        reporter.register_listener formatter, :dump_summary
        reporter.start 3, (start_time + 5)

        expect(formatter).to receive(:dump_summary) do |notification|
          expect(notification.load_time).to eq(5)
        end

        reporter.finish
      end
    end

    describe "#notify_non_example_exception" do
      it "sends a `message` notification that contains the formatted exception details" do
        formatter_out = StringIO.new
        formatter = Formatters::ProgressFormatter.new(formatter_out)
        reporter.register_listener formatter, :message

        line = __LINE__ + 1
        exception = 1 / 0 rescue $!
        reporter.notify_non_example_exception(exception, "NonExample Context")

        expect(formatter_out.string).to start_with(<<-EOS.gsub(/^ +\|/, '').chomp)
          |
          |NonExample Context
          |Failure/Error: exception = 1 / 0 rescue $!
          |
          |ZeroDivisionError:
          |  divided by 0
          |# #{Metadata.relative_path(__FILE__)}:#{line}
        EOS
      end

      it "records the fact that a non example failure has occurred" do
        expect {
          reporter.notify_non_example_exception(Exception.new, "NonExample Context")
        }.to change(world, :non_example_failure).from(a_falsey_value).to(true)
      end
    end
  end
end

<MSG> formalize Reporter#report as an API to support extensions like interactive_rspec

<DFF> @@ -82,5 +82,22 @@ module RSpec::Core
         reporter.example_started(example)
       end
     end
+
+    describe "#report" do
+      it "supports one arg (count)" do
+        Reporter.new.report(1) {}
+      end
+
+      it "supports two args (count, seed)" do
+        Reporter.new.report(1, 2) {}
+      end
+
+      it "yields itself" do
+        reporter = Reporter.new
+        yielded = nil
+        reporter.report(3) {|r| yielded = r}
+        yielded.should eq(reporter)
+      end
+    end
   end
 end

{'commit': 'diff --git a/Changelog.md b/Changelog.md\nindex a1e3dfcb..8c9c4b25 100644\n--- a/Changelog.md\n+++ b/Changelog.md\n@@ -9,6 +9,8 @@\n   * SharedContext supports `let` (David Chelimsky)\n   * Override opposing tags from the command line (David Chelimsky)\n   * Eliminate Ruby warnings (Matijs van Zuijlen)\n+  * Make reporter.report an API (David Chelimsky)\n+    * supports extension tools like interative_rspec\n \n * Bug fixes\n   * Make sure the `bar` in `--tag foo:bar` makes it to DRb (Aaron Gibralter)\ndiff --git a/Gemfile b/Gemfile\nindex 2176a6fc..35a599df 100644\n--- a/Gemfile\n+++ b/Gemfile\n@@ -57,3 +57,4 @@ group :development do\n     gem "ruby-prof", "~> 0.10.0"\n   end\n end\n+gem \'interactive_rspec\'\ndiff --git a/lib/rspec/core/reporter.rb b/lib/rspec/core/reporter.rb\nindex f0fc5bb1..d15f02c7 100644\n--- a/lib/rspec/core/reporter.rb\n+++ b/lib/rspec/core/reporter.rb\n@@ -6,8 +6,30 @@ module RSpec::Core\n       @duration = @start = nil\n     end\n \n-    def report(count, seed)\n-      start(count)\n+    # @api\n+    # @overload report(count, &block)\n+    # @overload report(count, seed, &block)\n+    # @param [Fixnum] count the number of examples being run\n+    # @param [Fixnum] seed (optional) the seed used to randomize the spec run\n+    # @param [Block] block yields itself for further reporting.\n+    #\n+    # Initializes the report run and yields itself for further reporting. The\n+    # block is required, so that the reporter can manage cleaning up after the\n+    # run.\n+    #\n+    # ### WARNING\n+    #\n+    # The `seed` argument is an internal API and is not guaranteed to be\n+    # supported in the future.\n+    #\n+    # @example\n+    #\n+    #     reporter.report(group.examples.size) do |r|\n+    #       example_groups.map {|g| g.run(r) }\n+    #     end\n+    #\n+    def report(expected_example_count, seed=nil)\n+      start(expected_example_count)\n       begin\n         yield self\n       ensure\n@@ -15,21 +37,6 @@ module RSpec::Core\n       end\n     end\n \n-    def finish(seed)\n-      begin\n-        stop\n-        notify :start_dump\n-        notify :dump_pending\n-        notify :dump_failures\n-        notify :dump_summary, @duration, @example_count, @failure_count, @pending_count\n-        notify :seed, seed if seed\n-      ensure\n-        notify :close\n-      end\n-    end\n-\n-    alias_method :abort, :finish\n-\n     def start(expected_example_count)\n       @start = Time.now\n       notify :start, expected_example_count\n@@ -66,6 +73,21 @@ module RSpec::Core\n       notify :example_pending, example\n     end\n \n+    def finish(seed)\n+      begin\n+        stop\n+        notify :start_dump\n+        notify :dump_pending\n+        notify :dump_failures\n+        notify :dump_summary, @duration, @example_count, @failure_count, @pending_count\n+        notify :seed, seed if seed\n+      ensure\n+        notify :close\n+      end\n+    end\n+\n+    alias_method :abort, :finish\n+\n     def stop\n       @duration = Time.now - @start if @start\n       notify :stop\ndiff --git a/spec/rspec/core/reporter_spec.rb b/spec/rspec/core/reporter_spec.rb\nindex fa5a8c06..1bb211fa 100644\n--- a/spec/rspec/core/reporter_spec.rb\n+++ b/spec/rspec/core/reporter_spec.rb\n@@ -82,5 +82,22 @@ module RSpec::Core\n         reporter.example_started(example)\n       end\n     end\n+\n+    describe "#report" do\n+      it "supports one arg (count)" do\n+        Reporter.new.report(1) {}\n+      end\n+\n+      it "supports two args (count, seed)" do\n+        Reporter.new.report(1, 2) {}\n+      end\n+\n+      it "yields itself" do\n+        reporter = Reporter.new\n+        yielded = nil\n+        reporter.report(3) {|r| yielded = r}\n+        yielded.should eq(reporter)\n+      end\n+    end\n   end\n end\n', 'commit_subject': 'formalize Reporter#report as an API to support extensions like interactive_rspec', 'repo_name': 'rspec/rspec-core', 'committer': {'name': 'David Chelimsky', 'email': 'f7cb1b4a2064ae0fe0ec6b9386cc618064212b58@gmail.com', 'time_sec': '1320152585', 'tz_offset': '-300', 'date': {'seconds': '1320152585'}}, 'commit_tree': '4b235cb88e5d0779e065883e1037150f0e633b18', 'commit_message': 'formalize Reporter#report as an API to support extensions like interactive_rspec\n', 'language_name': 'Shell', 'license': 'mit', 'hunks': '@@ -82,5 +82,22 @@ module RSpec::Core\n         reporter.example_started(example)\n       end\n     end\n+\n+    describe "#report" do\n+      it "supports one arg (count)" do\n+        Reporter.new.report(1) {}\n+      end\n+\n+      it "supports two args (count, seed)" do\n+        Reporter.new.report(1, 2) {}\n+      end\n+\n+      it "yields itself" do\n+        reporter = Reporter.new\n+        yielded = nil\n+        reporter.report(3) {|r| yielded = r}\n+        yielded.should eq(reporter)\n+      end\n+    end\n   end\n end\n', 'addition_count': 17, 'deletion_count': 0, 'src_file': 'a/spec/rspec/core/reporter_spec.rb', 'tgt_file': 'b/spec/rspec/core/reporter_spec.rb', 'file_extension': '.rb', 'before_file': 'module RSpec::Core\n  RSpec.describe Reporter do\n    include FormatterSupport\n\n    let(:config)   { Configuration.new }\n    let(:world)    { World.new(config) }\n    let(:reporter) { Reporter.new config }\n    let(:start_time) { Time.now }\n    let(:example) { super() }\n\n    describe "finish" do\n      let(:formatter) { double("formatter") }\n\n      %w[start_dump dump_pending dump_failures dump_summary close].map(&:to_sym).each do |message|\n        it "sends #{message} to the formatter(s) that respond to message" do\n          reporter.register_listener formatter, message\n          expect(formatter.as_null_object).to receive(message)\n          reporter.finish\n        end\n\n        it "doesnt notify formatters about messages they dont implement" do\n          expect { reporter.finish }.to_not raise_error\n        end\n      end\n\n      it "dumps the failure summary after the profile and deprecation summary so failures don\'t scroll off the screen and get missed" do\n        config.profile_examples = 10\n        formatter = instance_double("RSpec::Core::Formatter::ProgressFormatter")\n        reporter.register_listener(formatter, :dump_summary, :dump_profile, :deprecation_summary)\n\n        expect(formatter).to receive(:deprecation_summary).ordered\n        expect(formatter).to receive(:dump_profile).ordered\n        expect(formatter).to receive(:dump_summary).ordered\n\n        reporter.finish\n      end\n\n      it "allows the profiler to be used without being manually setup" do\n        config.profile_examples = true\n        expect {\n          reporter.finish\n        }.to_not raise_error\n      end\n    end\n\n    describe \'start\' do\n      before { config.start_time = start_time }\n\n      it \'notifies the formatter of start with example count\' do\n        formatter = double("formatter")\n        reporter.register_listener formatter, :start\n\n        expect(formatter).to receive(:start) do |notification|\n          expect(notification.count).to eq 3\n          expect(notification.load_time).to eq 5\n        end\n\n        reporter.start 3, (start_time + 5)\n      end\n\n      it \'notifies the formatter of the seed used before notifing of start\' do\n        formatter = double("formatter")\n        reporter.register_listener formatter, :seed\n        reporter.register_listener formatter, :start\n        expect(formatter).to receive(:seed).ordered.with(\n          an_object_having_attributes(:seed => config.seed, :seed_used? => config.seed_used?)\n        )\n        expect(formatter).to receive(:start).ordered\n        reporter.start 1\n      end\n    end\n\n    context "given one formatter" do\n      it "passes messages to that formatter" do\n        formatter = double("formatter", :example_started => nil)\n        reporter.register_listener formatter, :example_started\n        example = new_example\n\n        expect(formatter).to receive(:example_started) do |notification|\n          expect(notification.example).to eq example\n        end\n        reporter.example_started(example)\n      end\n    end\n  end\nend\n          example("passing example") {}\n          example("pending example", :skip => true) { }\n        end\n        group.describe("context 2") do\n          example("failed example") { fail }\n        end\n\n        group.run(reporter)\n\n        expect(order).to eq([\n           "Started: root",\n           "Started: context 1",\n           "Started Example",\n           "Finished Example",\n           "Passed",\n           "Started Example",\n           "Finished Example",\n           "Pending",\n           "Finished: context 1",\n           "Started: context 2",\n           "Started Example",\n           "Finished Example",\n           "Failed",\n           "Finished: context 2",\n           "Finished: root"\n        ])\n      end\n    end\n\n    context "given an example group with no examples" do\n      it "does not pass example_group_started or example_group_finished to formatter" do\n        formatter = double("formatter")\n        expect(formatter).not_to receive(:example_group_started)\n        expect(formatter).not_to receive(:example_group_finished)\n\n        reporter.register_listener formatter, :example_group_started, :example_group_finished\n\n        group = RSpec.describe("root")\n\n        group.run(reporter)\n      end\n    end\n\n    context "given multiple formatters" do\n      it "passes messages to all formatters" do\n        formatters = (1..2).map { double("formatter", :example_started => nil) }\n        example = new_example\n\n        formatters.each do |formatter|\n          expect(formatter).to receive(:example_started) do |notification|\n            expect(notification.example).to eq example\n          end\n          reporter.register_listener formatter, :example_started\n        end\n\n        reporter.example_started(example)\n      end\n    end\n\n    describe "#exit_early" do\n      it "returns the passed exit code" do\n        expect(reporter.exit_early(42)).to eq(42)\n      end\n\n      it "sends a complete cycle of notifications" do\n        formatter = double("formatter")\n        %w[seed start start_dump dump_pending dump_failures dump_summary seed close].map(&:to_sym).each do |message|\n          reporter.register_listener formatter, message\n          expect(formatter).to receive(message).ordered\n        end\n        reporter.exit_early(42)\n      end\n    end\n\n    describe "#report" do\n      it "supports one arg (count)" do\n        reporter.report(1) {}\n      end\n\n      it "yields itself" do\n        yielded = nil\n        reporter.report(3) { |r| yielded = r }\n        expect(yielded).to eq(reporter)\n      end\n    end\n\n    describe "#register_listener" do\n      let(:listener) { double("listener", :start => nil) }\n\n      before { reporter.register_listener listener, :start }\n\n      it \'will register the listener to specified notifications\' do\n        expect(reporter.registered_listeners :start).to eq [listener]\n      end\n\n      it \'will match string notification names\' do\n        reporter.register_listener listener, "stop"\n        expect(reporter.registered_listeners :stop).to eq [listener]\n      end\n\n      it \'will send notifications when a subscribed event is triggered\' do\n        expect(listener).to receive(:start) do |notification|\n          expect(notification.count).to eq 42\n        end\n        reporter.start 42\n      end\n\n      it \'will ignore duplicated listeners\' do\n        reporter.register_listener listener, :start\n        expect(listener).to receive(:start).once\n        reporter.start 42\n      end\n    end\n\n    describe "#publish" do\n      let(:listener) { double("listener", :custom => nil) }\n      before do\n        reporter.register_listener listener, :custom, :start\n      end\n\n      it \'will send custom events to registered listeners\' do\n        expect(listener).to receive(:custom).with(RSpec::Core::Notifications::NullNotification)\n        reporter.publish :custom\n      end\n\n      it \'will raise when encountering RSpec standard events\' do\n        expect { reporter.publish :start }.to raise_error(\n          StandardError,\n          a_string_including("not internal RSpec ones")\n        )\n      end\n\n      it \'will ignore event names sent as strings\' do\n        expect(listener).not_to receive(:custom)\n        reporter.publish "custom"\n      end\n\n      it \'will provide a custom notification object based on the options hash\' do\n        expect(listener).to receive(:custom).with(\n          an_object_having_attributes(:my_data => :value)\n        )\n        reporter.publish :custom, :my_data => :value\n      end\n    end\n\n    describe "#abort_with" do\n      before { allow(reporter).to receive(:exit!) }\n\n      it "publishes the message and notifies :close" do\n        listener = double("Listener")\n        reporter.register_listener(listener, :message, :close)\n        stream   = StringIO.new\n\n        allow(listener).to receive(:message) { |n| stream << n.message }\n        allow(listener).to receive(:close) { stream.close }\n\n        reporter.register_listener(listener)\n        reporter.abort_with("Booom!", 1)\n\n        expect(stream).to have_attributes(:string => "Booom!").and be_closed\n      end\n\n      it "exits with the provided exit code" do\n        reporter.abort_with("msg", 13)\n        expect(reporter).to have_received(:exit!).with(13)\n      end\n    end\n\n    describe "timing" do\n      before do\n        config.start_time = start_time\n      end\n\n      it "uses RSpec::Core::Time as to not be affected by changes to time in examples" do\n        formatter = double(:formatter)\n        reporter.register_listener formatter, :dump_summary\n        reporter.start 1\n        allow(Time).to receive_messages(:now => ::Time.utc(2012, 10, 1))\n\n        duration = nil\n        allow(formatter).to receive(:dump_summary) do |notification|\n          duration = notification.duration\n        end\n\n        reporter.finish\n        expect(duration).to be < 0.2\n      end\n\n      it "captures the load time so it can report it later" do\n        formatter = instance_double("ProgressFormatter")\n        reporter.register_listener formatter, :dump_summary\n        reporter.start 3, (start_time + 5)\n\n        expect(formatter).to receive(:dump_summary) do |notification|\n          expect(notification.load_time).to eq(5)\n        end\n\n        reporter.finish\n      end\n    end\n\n    describe "#notify_non_example_exception" do\n      it "sends a `message` notification that contains the formatted exception details" do\n        formatter_out = StringIO.new\n        formatter = Formatters::ProgressFormatter.new(formatter_out)\n        reporter.register_listener formatter, :message\n\n        line = __LINE__ + 1\n        exception = 1 / 0 rescue $!\n        reporter.notify_non_example_exception(exception, "NonExample Context")\n\n        expect(formatter_out.string).to start_with(<<-EOS.gsub(/^ +\\|/, \'\').chomp)\n          |\n          |NonExample Context\n          |Failure/Error: exception = 1 / 0 rescue $!\n          |\n          |ZeroDivisionError:\n          |  divided by 0\n          |# #{Metadata.relative_path(__FILE__)}:#{line}\n        EOS\n      end\n\n      it "records the fact that a non example failure has occurred" do\n        expect {\n          reporter.notify_non_example_exception(Exception.new, "NonExample Context")\n        }.to change(world, :non_example_failure).from(a_falsey_value).to(true)\n      end\n    end\n  end\nend\n', 'message': 'formalize Reporter#report as an API to support extensions like interactive_rspec\n', 'author_name': 'David Chelimsky', 'lang': 'rb'}