Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
extract formatter management
  • Loading branch information
JonRowe committed Jan 23, 2014
1 parent 9077b9b commit 66ec1e4
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 162 deletions.
4 changes: 2 additions & 2 deletions features/configuration/read_options_from_file.feature
Expand Up @@ -45,7 +45,7 @@ Feature: read command line configuration options from files
"""ruby
describe "formatter set in custom options file" do
it "sets formatter" do
expect(RSpec.configuration.formatters.first).
expect(RSpec.configuration.formatters.to_a.first).
to be_a(RSpec::Core::Formatters::DocumentationFormatter)
end
end
Expand Down Expand Up @@ -82,7 +82,7 @@ Feature: read command line configuration options from files
"""ruby
describe "formatter" do
it "is set to documentation" do
expect(RSpec.configuration.formatters.first).to be_an(RSpec::Core::Formatters::DocumentationFormatter)
expect(RSpec.configuration.formatters.to_a.first).to be_an(RSpec::Core::Formatters::DocumentationFormatter)
end
end
"""
Expand Down
1 change: 1 addition & 0 deletions lib/rspec/core/command_line.rb
Expand Up @@ -20,6 +20,7 @@ def run(err, out)
@configuration.output_stream = out if @configuration.output_stream == $stdout
@options.configure(@configuration)
@configuration.load_spec_files
@configuration.setup_default_formatters
@world.announce_filters

@configuration.reporter.report(@world.example_count) do |reporter|
Expand Down
90 changes: 11 additions & 79 deletions lib/rspec/core/configuration.rb
Expand Up @@ -255,7 +255,6 @@ def initialize
@include_or_extend_modules = []
@mock_framework = nil
@files_to_run = []
@formatters = []
@color = false
@pattern = '**/*_spec.rb'
@failure_exit_code = 1
Expand Down Expand Up @@ -294,7 +293,7 @@ def force(hash)
def reset
@spec_files_loaded = false
@reporter = nil
@formatters.clear
@formatters = nil
end

# @overload add_setting(name)
Expand Down Expand Up @@ -593,28 +592,24 @@ def full_description
# and paths to use for output streams, but you should consider that a
# private api that may change at any time without notice.
def add_formatter(formatter_to_use, *paths)
formatter_class =
built_in_formatter(formatter_to_use) ||
custom_formatter(formatter_to_use) ||
(raise ArgumentError, "Formatter '#{formatter_to_use}' unknown - maybe you meant 'documentation' or 'progress'?.")

paths << output_stream if paths.empty?
new_formatter = formatter_class.new(*paths.map {|p| String === p ? file_at(p) : p})
formatters << new_formatter unless duplicate_formatter_exists?(new_formatter)
formatters.add formatter_to_use, *paths
end

alias_method :formatter=, :add_formatter

# @api private
def formatters
@formatters ||= []
@formatters ||= Formatters::Collection.new(reporter)
end

# @api private
def setup_default_formatters
formatters.setup_default output_stream, deprecation_stream
end

# @api private
def reporter
@reporter ||= begin
add_formatter('progress') if formatters.empty?
add_formatter(RSpec::Core::Formatters::DeprecationFormatter, deprecation_stream, output_stream)
Reporter.new(self, *formatters)
end
@reporter ||= Reporter.new(self)
end

# @api private
Expand Down Expand Up @@ -1098,69 +1093,6 @@ def assert_no_example_groups_defined(config_option)
def output_to_tty?(output=output_stream)
tty? || (output.respond_to?(:tty?) && output.tty?)
end

def built_in_formatter(key)
case key.to_s
when 'd', 'doc', 'documentation', 's', 'n', 'spec', 'nested'
require 'rspec/core/formatters/documentation_formatter'
RSpec::Core::Formatters::DocumentationFormatter
when 'h', 'html'
require 'rspec/core/formatters/html_formatter'
RSpec::Core::Formatters::HtmlFormatter
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

def custom_formatter(formatter_ref)
if Class === formatter_ref
formatter_ref
elsif string_const?(formatter_ref)
begin
formatter_ref.gsub(/^::/,'').split('::').inject(Object) { |const,string| const.const_get string }
rescue NameError
require( path_for(formatter_ref) ) ? retry : raise
end
end
end

def duplicate_formatter_exists?(new_formatter)
formatters.any? do |formatter|
formatter.class === new_formatter && formatter.output == new_formatter.output
end
end

def string_const?(str)
str.is_a?(String) && /\A[A-Z][a-zA-Z0-9_:]*\z/ =~ str
end

def path_for(const_ref)
underscore_with_fix_for_non_standard_rspec_naming(const_ref)
end

def underscore_with_fix_for_non_standard_rspec_naming(string)
underscore(string).sub(%r{(^|/)r_spec($|/)}, '\\1rspec\\2')
end

# activesupport/lib/active_support/inflector/methods.rb, line 48
def underscore(camel_cased_word)
word = camel_cased_word.to_s.dup
word.gsub!(/::/, '/')
word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
word.tr!("-", "_")
word.downcase!
word
end

def file_at(path)
FileUtils.mkdir_p(File.dirname(path))
File.new(path, 'w')
end
end
end
end
110 changes: 110 additions & 0 deletions lib/rspec/core/formatters.rb
Expand Up @@ -51,4 +51,114 @@
# @see RSpec::Core::Formatters::BaseTextFormatter
# @see RSpec::Core::Reporter
module RSpec::Core::Formatters

class Collection
def initialize(reporter)
@formatters = []
@reporter = reporter
end

# @api private
def setup_default output_stream, deprecation_stream
if @formatters.empty?
add 'progress', output_stream
end
add DeprecationFormatter, deprecation_stream, output_stream
end

# @api private
def add formatter_to_use, *paths
formatter_class =
built_in_formatter(formatter_to_use) ||
custom_formatter(formatter_to_use) ||
(raise ArgumentError, "Formatter '#{formatter_to_use}' unknown - maybe you meant 'documentation' or 'progress'?.")
formatter = formatter_class.new(*paths.map {|p| String === p ? file_at(p) : p})

if formatter.respond_to?(:notifications)
@reporter.register_listener formatter, *formatter.notifications
@formatters << formatter unless duplicate_formatter_exists?(formatter)
else
raise 'Legacy formatter support yet to be implemented'
end
end

# @api private
def clear
@formatters.clear
end

def empty?
@formatters.empty?
end

# @api private
def to_a
@formatters
end

private

def duplicate_formatter_exists?(new_formatter)
@formatters.any? do |formatter|
formatter.class === new_formatter && formatter.output == new_formatter.output
end
end

def built_in_formatter(key)
case key.to_s
when 'd', 'doc', 'documentation', 's', 'n', 'spec', 'nested'
require 'rspec/core/formatters/documentation_formatter'
DocumentationFormatter
when 'h', 'html'
require 'rspec/core/formatters/html_formatter'
HtmlFormatter
when 'p', 'progress'
require 'rspec/core/formatters/progress_formatter'
ProgressFormatter
when 'j', 'json'
require 'rspec/core/formatters/json_formatter'
JsonFormatter
end
end

def custom_formatter(formatter_ref)
if Class === formatter_ref
formatter_ref
elsif string_const?(formatter_ref)
begin
formatter_ref.gsub(/^::/,'').split('::').inject(Object) { |const,string| const.const_get string }
rescue NameError
require( path_for(formatter_ref) ) ? retry : raise
end
end
end

def string_const?(str)
str.is_a?(String) && /\A[A-Z][a-zA-Z0-9_:]*\z/ =~ str
end

def path_for(const_ref)
underscore_with_fix_for_non_standard_rspec_naming(const_ref)
end

def underscore_with_fix_for_non_standard_rspec_naming(string)
underscore(string).sub(%r{(^|/)r_spec($|/)}, '\\1rspec\\2')
end

# activesupport/lib/active_support/inflector/methods.rb, line 48
def underscore(camel_cased_word)
word = camel_cased_word.to_s.dup
word.gsub!(/::/, '/')
word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
word.tr!("-", "_")
word.downcase!
word
end

def file_at(path)
FileUtils.mkdir_p(File.dirname(path))
File.new(path, 'w')
end
end
end
87 changes: 6 additions & 81 deletions spec/rspec/core/configuration_spec.rb
Expand Up @@ -806,87 +806,11 @@ def metadata_hash(*args)
end
end

describe '#formatter=' do
it "delegates to add_formatter (better API for user-facing configuration)" do
expect(config).to receive(:add_formatter).with('these','options')
config.add_formatter('these','options')
end
end

describe "#add_formatter" do
let(:path) { File.join(Dir.tmpdir, 'output.txt') }

it "adds to the list of formatters" do
config.add_formatter :documentation
expect(config.formatters.first).to be_an_instance_of(Formatters::DocumentationFormatter)
end

it "finds a formatter by name (w/ Symbol)" do
config.add_formatter :documentation
expect(config.formatters.first).to be_an_instance_of(Formatters::DocumentationFormatter)
end

it "finds a formatter by name (w/ String)" do
config.add_formatter 'documentation'
expect(config.formatters.first).to be_an_instance_of(Formatters::DocumentationFormatter)
end

it "finds a formatter by class" do
formatter_class = Class.new(Formatters::BaseTextFormatter)
config.add_formatter formatter_class
expect(config.formatters.first).to be_an_instance_of(formatter_class)
end

it "finds a formatter by class name" do
stub_const("CustomFormatter", Class.new(Formatters::BaseFormatter))
config.add_formatter "CustomFormatter"
expect(config.formatters.first).to be_an_instance_of(CustomFormatter)
end

it "finds a formatter by class fully qualified name" do
stub_const("RSpec::CustomFormatter", Class.new(Formatters::BaseFormatter))
config.add_formatter "RSpec::CustomFormatter"
expect(config.formatters.first).to be_an_instance_of(RSpec::CustomFormatter)
end

it "requires a formatter file based on its fully qualified name" do
expect(config).to receive(:require).with('rspec/custom_formatter') do
stub_const("RSpec::CustomFormatter", Class.new(Formatters::BaseFormatter))
end
config.add_formatter "RSpec::CustomFormatter"
expect(config.formatters.first).to be_an_instance_of(RSpec::CustomFormatter)
end

it "raises NameError if class is unresolvable" do
expect(config).to receive(:require).with('rspec/custom_formatter3')
expect(lambda { config.add_formatter "RSpec::CustomFormatter3" }).to raise_error(NameError)
end

it "raises ArgumentError if formatter is unknown" do
expect(lambda { config.add_formatter :progresss }).to raise_error(ArgumentError)
end

context "with a 2nd arg defining the output" do
it "creates a file at that path and sets it as the output" do
config.add_formatter('doc', path)
expect(config.formatters.first.output).to be_a(File)
expect(config.formatters.first.output.path).to eq(path)
end
end

context "when a duplicate formatter exists" do
before { config.add_formatter :documentation }

it "doesn't add the formatter for the same output target" do
expect {
config.add_formatter :documentation
}.not_to change { config.formatters.length }
end

it "adds the formatter for different output targets" do
expect {
config.add_formatter :documentation, path
}.to change { config.formatters.length }
%w[formatter= add_formatter].each do |config_method|
describe "##{config_method}" do
it "delegates to formatters#add" do
expect(config.formatters).to receive(:add).with('these','options')
config.send(config_method,'these','options')
end
end
end
Expand Down Expand Up @@ -1493,6 +1417,7 @@ def strategy.order(list)
it 'causes deprecations to raise errors rather than printing to the deprecation stream' do
config.deprecation_stream = stream = StringIO.new
config.raise_errors_for_deprecations!
config.setup_default_formatters

expect {
config.reporter.deprecation(:deprecated => "foo", :call_site => "foo.rb:1")
Expand Down

0 comments on commit 66ec1e4

Please sign in to comment.