Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Restore "Merge pull request #609 from alexch/json-formatter""

Most of the commits with this merge are fine - only one is causing failures
in some rubies - we can deal with that issue separately.

This reverts commit 359cd38.
  • Loading branch information...
commit afa42508558593e56acf91f24653c218cbff6558 1 parent 359cd38
@dchelimsky dchelimsky authored
View
22 features/formatters/json_formatter.feature
@@ -0,0 +1,22 @@
+Feature: JSON formatter
+
+ Scenario: Formatting example names for retry
+ Given a file named "various_spec.rb" with:
+ """
+ describe "Various" do
+ it "fails" do
+ "fail".should eq("succeed")
+ end
+
+ it "succeeds" do
+ "succeed".should eq("succeed")
+ end
+
+ it "pends"
+ end
+ """
+ When I run `rspec various_spec.rb --format j`
+ Then the output should contain all of these:
+ |"summary_line":"3 examples, 1 failure, 1 pending"}|
+ |{"examples":[{"description":"fails","full_description":"Various fails","status":"failed","file_path":"./various_spec.rb","line_number":2,"exception":|
+ And the exit status should be 1
View
3  lib/rspec/core/configuration.rb
@@ -878,6 +878,9 @@ def built_in_formatter(key)
when 'p', 'progress'
require 'rspec/core/formatters/progress_formatter'
RSpec::Core::Formatters::ProgressFormatter
+ when 'j', 'json'
+ require 'rspec/core/formatters/json_formatter'
+ RSpec::Core::Formatters::JsonFormatter
end
end
View
25 lib/rspec/core/formatters/base_formatter.rb
@@ -5,6 +5,24 @@ module RSpec
module Core
module Formatters
+ # The Reporter calls the Formatter with this protocol:
+ #
+ # * start(expected_example_count)
+ # * zero or more of the following
+ # * example_group_started(group)
+ # * example_started(example)
+ # * example_passed(example)
+ # * example_failed(example)
+ # * example_pending(example)
+ # * message(string)
+ # * stop
+ # * start_dump
+ # * dump_pending
+ # * dump_failures
+ # * dump_summary(duration, example_count, failure_count, pending_count)
+ # * seed(value)
+ # * close
+ #
class BaseFormatter
include Helpers
attr_accessor :example_group
@@ -68,7 +86,8 @@ def stop
end
# This method is invoked after all of the examples have executed. The next method
- # to be invoked after this one is #dump_failure (once for each failed example),
+ # to be invoked after this one is #dump_failures
+ # (BaseTextFormatter then calls #dump_failure once for each failed example.)
def start_dump
end
@@ -117,6 +136,8 @@ def configuration
def backtrace_line(line)
return nil if configuration.cleaned_from_backtrace?(line)
RSpec::Core::Metadata::relative_path(line)
+ rescue SecurityError
+ nil
end
def read_failed_line(exception, example)
@@ -131,6 +152,8 @@ def read_failed_line(exception, example)
else
"Unable to find #{file_path} to read failed line"
end
+ rescue SecurityError
+ "Unable to read failed line"
end
def find_failed_line(backtrace, path)
View
73 lib/rspec/core/formatters/json_formatter.rb
@@ -0,0 +1,73 @@
+require 'rspec/core/formatters/base_formatter'
+require 'json'
+
+module RSpec
+ module Core
+ module Formatters
+
+ class JsonFormatter < BaseFormatter
+
+ attr_reader :output_hash
+
+ def initialize(output)
+ super
+ @output_hash = {}
+ end
+
+ def message(message)
+ (@output_hash[:messages] ||= []) << message
+ end
+
+ def dump_summary(duration, example_count, failure_count, pending_count)
+ super(duration, example_count, failure_count, pending_count)
+ @output_hash[:summary] = {
+ :duration => duration,
+ :example_count => example_count,
+ :failure_count => failure_count,
+ :pending_count => pending_count
+ }
+ @output_hash[:summary_line] = summary_line(example_count, failure_count, pending_count)
+
+ # Don't print out profiled info if there are failures, it just clutters the output
+ dump_profile if profile_examples? && failure_count == 0
+ end
+
+ def summary_line(example_count, failure_count, pending_count)
+ summary = pluralize(example_count, "example")
+ summary << ", " << pluralize(failure_count, "failure")
+ summary << ", #{pending_count} pending" if pending_count > 0
+ summary
+ end
+
+ def stop
+ super
+ @output_hash[:examples] = examples.map do |example|
+ {
+ :description => example.description,
+ :full_description => example.full_description,
+ :status => example.execution_result[:status],
+ # :example_group,
+ # :execution_result,
+ :file_path => example.metadata[:file_path],
+ :line_number => example.metadata[:line_number],
+ }.tap do |hash|
+ if e=example.exception
+ hash[:exception] = {
+ :class => e.class.name,
+ :message => e.message,
+ :backtrace => e.backtrace,
+ }
+ end
+ end
+ end
+ end
+
+ def close
+ output.write @output_hash.to_json
+ output.close if IO === output && output != $stdout
+ end
+
+ end
+ end
+ end
+end
View
2  lib/rspec/core/metadata.rb
@@ -31,6 +31,8 @@ def self.relative_path(line)
line = line.sub(/\A([^:]+:\d+)$/, '\\1')
return nil if line == '-e:1'
line
+ rescue SecurityError
+ nil
end
# @private
View
16 spec/rspec/core/formatters/base_formatter_spec.rb
@@ -16,6 +16,12 @@
formatter.__send__(:backtrace_line, original_line)
original_line.should eq(File.expand_path(__FILE__))
end
+
+ it "deals gracefully with a security error" do
+ safely do
+ formatter.__send__(:backtrace_line, __FILE__).should be_nil
+ end
+ end
end
describe "read_failed_line" do
@@ -32,6 +38,16 @@
}.should_not raise_error
end
+ it "deals gracefully with a security error" do
+ exception = mock(:Exception, :backtrace => [ "#{__FILE__}:#{__LINE__}"])
+ example = mock(:Example, :file_path => __FILE__)
+ safely do
+ lambda {
+ formatter.send(:read_failed_line, exception, example).should == "Unable to read failed line"
+ }.should_not raise_error
+ end
+ end
+
context "when String alias to_int to_i" do
before do
String.class_eval do
View
110 spec/rspec/core/formatters/json_formatter_spec.rb
@@ -0,0 +1,110 @@
+require 'spec_helper'
+require 'rspec/core/formatters/json_formatter'
+require 'json'
+require 'rspec/core/reporter'
+
+# todo, someday:
+# it "lists the groups (describe and context) separately"
+# it "includes full 'execution_result'"
+# it "relativizes backtrace paths"
+# it "includes profile information (implements dump_profile)"
+# it "shows the pending message if one was given"
+# it "shows the seed if run was randomized"
+# it "lists pending specs that were fixed"
+describe RSpec::Core::Formatters::JsonFormatter do
+ let(:output) { StringIO.new }
+ let(:formatter) { RSpec::Core::Formatters::JsonFormatter.new(output) }
+ let(:reporter) { RSpec::Core::Reporter.new(formatter) }
+
+ it "outputs json (brittle high level functional test)" do
+ group = RSpec::Core::ExampleGroup.describe("one apiece") do
+ it("succeeds") { 1.should == 1 }
+ it("fails") { fail "eek" }
+ it("pends") { pending "world peace" }
+ end
+ succeeding_line = __LINE__ - 4
+ failing_line = __LINE__ - 4
+ pending_line = __LINE__ - 4
+
+ now = Time.now
+ Time.stub(:now).and_return(now)
+ reporter.report(2) do |r|
+ group.run(r)
+ end
+
+ # grab the actual backtrace -- kind of a cheat
+ failing_backtrace = formatter.output_hash[:examples][1][:exception][:backtrace]
+ this_file = relative_path(__FILE__)
+
+ expected = {
+ :examples => [
+ {
+ :description => "succeeds",
+ :full_description => "one apiece succeeds",
+ :status => "passed",
+ :file_path => this_file,
+ :line_number => succeeding_line,
+ },
+ {
+ :description => "fails",
+ :full_description => "one apiece fails",
+ :status => "failed",
+ :file_path => this_file,
+ :line_number => failing_line,
+ :exception => {:class => "RuntimeError", :message => "eek", :backtrace => failing_backtrace}
+ },
+ {
+ :description => "pends",
+ :full_description => "one apiece pends",
+ :status => "pending",
+ :file_path => this_file,
+ :line_number => pending_line,
+ },
+ ],
+ :summary => {
+ :duration => formatter.output_hash[:summary][:duration],
+ :example_count => 3,
+ :failure_count => 1,
+ :pending_count => 1,
+ },
+ :summary_line => "3 examples, 1 failure, 1 pending"
+ }
+ formatter.output_hash.should == expected
+ output.string.should == expected.to_json
+ end
+
+ describe "#stop" do
+ it "adds all examples to the output hash" do
+ formatter.stop
+ formatter.output_hash[:examples].should_not be_nil
+ end
+ end
+
+ describe "#close" do
+ it "outputs the results as a JSON string" do
+ output.string.should == ""
+ formatter.close
+ output.string.should == {}.to_json
+ end
+ end
+
+ describe "#message" do
+ it "adds a message to the messages list" do
+ formatter.message("good job")
+ formatter.output_hash[:messages].should == ["good job"]
+ end
+ end
+
+ describe "#dump_summary" do
+ it "adds summary info to the output hash" do
+ duration, example_count, failure_count, pending_count = 1.0, 2, 1, 1
+ formatter.dump_summary(duration, example_count, failure_count, pending_count)
+ summary = formatter.output_hash[:summary]
+ %w(duration example_count failure_count pending_count).each do |key|
+ summary[key.to_sym].should == eval(key)
+ end
+ summary_line = formatter.output_hash[:summary_line]
+ summary_line.should == "2 examples, 1 failure, 1 pending"
+ end
+ end
+end
View
9 spec/rspec/core/formatters/snippet_extractor_spec.rb
@@ -12,6 +12,15 @@ module Formatters
it "falls back on a default message when it doesn't find the file" do
RSpec::Core::Formatters::SnippetExtractor.new.lines_around("blech", 8).should eq("# Couldn't get snippet for blech")
end
+
+ it "falls back on a default message when it gets a security error" do
+ Thread.new {
+ $SAFE = 3
+ $SAFE.should == 3
+ RSpec::Core::Formatters::SnippetExtractor.new.lines_around("blech", 8).should eq("# Couldn't get snippet for blech")
+ }.run
+ $SAFE.should == 0
+ end
end
end
end
View
20 spec/rspec/core/metadata_spec.rb
@@ -4,6 +4,26 @@ module RSpec
module Core
describe Metadata do
+ describe '.relative_path' do
+ let(:here) { File.expand_path(".") }
+ it "transforms absolute paths to relative paths" do
+ Metadata.relative_path(here).should == "."
+ end
+ it "transforms absolute paths to relative paths anywhere in its argument" do
+ Metadata.relative_path("foo #{here} bar").should == "foo . bar"
+ end
+ it "returns nil if passed an unparseable file:line combo" do
+ Metadata.relative_path("-e:1").should be_nil
+ end
+ # I have no idea what line = line.sub(/\A([^:]+:\d+)$/, '\\1') is supposed to do
+ it "gracefully returns nil if run in a secure thread" do
+ safely do
+ Metadata.relative_path(".").should be_nil
+ end
+ end
+
+ end
+
describe "#process" do
Metadata::RESERVED_KEYS.each do |key|
it "prohibits :#{key} as a hash key" do
View
9 spec/support/helper_methods.rb
@@ -2,4 +2,13 @@ module RSpecHelpers
def relative_path(path)
RSpec::Core::Metadata.relative_path(path)
end
+
+ def safely
+ Thread.new do
+ $SAFE = 3
+ yield
+ end.join
+ $SAFE.should == 0 # just to be safe ;-)
+ end
+
end
Please sign in to comment.
Something went wrong with that request. Please try again.