1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
|
RSpec::Support.require_rspec_support "directory_maker"
# ## Built-in Formatters
#
# * progress (default) - Prints dots for passing examples, `F` for failures, `*`
# for pending.
# * documentation - Prints the docstrings passed to `describe` and `it` methods
# (and their aliases).
# * html
# * json - Useful for archiving data for subsequent analysis.
#
# The progress formatter is the default, but you can choose any one or more of
# the other formatters by passing with the `--format` (or `-f` for short)
# command-line option, e.g.
#
# rspec --format documentation
#
# You can also send the output of multiple formatters to different streams, e.g.
#
# rspec --format documentation --format html --out results.html
#
# This example sends the output of the documentation formatter to `$stdout`, and
# the output of the html formatter to results.html.
#
# ## Custom Formatters
#
# You can tell RSpec to use a custom formatter by passing its path and name to
# the `rspec` command. For example, if you define MyCustomFormatter in
# path/to/my_custom_formatter.rb, you would type this command:
#
# rspec --require path/to/my_custom_formatter.rb --format MyCustomFormatter
#
# The reporter calls every formatter with this protocol:
#
# * To start
# * `start(StartNotification)`
# * Once per example group
# * `example_group_started(GroupNotification)`
# * Once per example
# * `example_started(ExampleNotification)`
# * One of these per example, depending on outcome
# * `example_passed(ExampleNotification)`
# * `example_failed(FailedExampleNotification)`
# * `example_pending(ExampleNotification)`
# * Optionally at any time
# * `message(MessageNotification)`
# * At the end of the suite
# * `stop(ExamplesNotification)`
# * `start_dump(NullNotification)`
# * `dump_pending(ExamplesNotification)`
# * `dump_failures(ExamplesNotification)`
# * `dump_summary(SummaryNotification)`
# * `seed(SeedNotification)`
# * `close(NullNotification)`
#
# Only the notifications to which you subscribe your formatter will be called
# on your formatter. To subscribe your formatter use:
# `RSpec::Core::Formatters#register` e.g.
#
# `RSpec::Core::Formatters.register FormatterClassName, :example_passed, :example_failed`
#
# We recommend you implement the methods yourself; for simplicity we provide the
# default formatter output via our notification objects but if you prefer you
# can subclass `RSpec::Core::Formatters::BaseTextFormatter` and override the
# methods you wish to enhance.
#
# @see RSpec::Core::Formatters::BaseTextFormatter
# @see RSpec::Core::Reporter
module RSpec::Core::Formatters
autoload :DocumentationFormatter, 'rspec/core/formatters/documentation_formatter'
autoload :HtmlFormatter, 'rspec/core/formatters/html_formatter'
autoload :FallbackMessageFormatter, 'rspec/core/formatters/fallback_message_formatter'
autoload :ProgressFormatter, 'rspec/core/formatters/progress_formatter'
autoload :ProfileFormatter, 'rspec/core/formatters/profile_formatter'
autoload :JsonFormatter, 'rspec/core/formatters/json_formatter'
autoload :BisectDRbFormatter, 'rspec/core/formatters/bisect_drb_formatter'
autoload :ExceptionPresenter, 'rspec/core/formatters/exception_presenter'
autoload :FailureListFormatter, 'rspec/core/formatters/failure_list_formatter'
# Register the formatter class
# @param formatter_class [Class] formatter class to register
# @param notifications [Array<Symbol>] one or more notifications to be
# registered to the specified formatter
#
# @see RSpec::Core::Formatters::BaseFormatter
def self.register(formatter_class, *notifications)
Loader.formatters[formatter_class] = notifications
end
# @api private
#
# `RSpec::Core::Formatters::Loader` is an internal class for
# managing formatters used by a particular configuration. It is
# not expected to be used directly, but only through the configuration
# interface.
class Loader
# @api private
#
# Internal formatters are stored here when loaded.
def self.formatters
@formatters ||= {}
end
# @api private
def initialize(reporter)
@formatters = []
@reporter = reporter
self.default_formatter = 'progress'
end
# @return [Array] the loaded formatters
attr_reader :formatters
# @return [Reporter] the reporter
attr_reader :reporter
# @return [String] the default formatter to setup, defaults to `progress`
attr_accessor :default_formatter
# @private
def prepare_default(output_stream, deprecation_stream)
reporter.prepare_default(self, output_stream, deprecation_stream)
end
# @private
def setup_default(output_stream, deprecation_stream)
add default_formatter, output_stream if @formatters.empty?
unless @formatters.any? { |formatter| DeprecationFormatter === formatter }
add DeprecationFormatter, deprecation_stream, output_stream
end
unless existing_formatter_implements?(:message)
add FallbackMessageFormatter, output_stream
end
return unless RSpec.configuration.profile_examples?
return if existing_formatter_implements?(:dump_profile)
add RSpec::Core::Formatters::ProfileFormatter, output_stream
end
# @private
def add(formatter_to_use, *paths)
# If a formatter instance was passed, we can register it directly,
# with no need for any of the further processing that happens below.
if Loader.formatters.key?(formatter_to_use.class)
register formatter_to_use, notifications_for(formatter_to_use.class)
return
end
formatter_class = find_formatter(formatter_to_use)
args = paths.map { |p| p.respond_to?(:puts) ? p : open_stream(p) }
if !Loader.formatters[formatter_class].nil?
formatter = formatter_class.new(*args)
register formatter, notifications_for(formatter_class)
elsif defined?(RSpec::LegacyFormatters)
formatter = RSpec::LegacyFormatters.load_formatter formatter_class, *args
register formatter, formatter.notifications
else
call_site = "Formatter added at: #{::RSpec::CallerFilter.first_non_rspec_line}"
RSpec.warn_deprecation <<-WARNING.gsub(/\s*\|/, ' ')
|The #{formatter_class} formatter uses the deprecated formatter
|interface not supported directly by RSpec 3.
|
|To continue to use this formatter you must install the
|`rspec-legacy_formatters` gem, which provides support
|for legacy formatters or upgrade the formatter to a
|compatible version.
|
|#{call_site}
WARNING
end
end
private
def find_formatter(formatter_to_use)
built_in_formatter(formatter_to_use) ||
custom_formatter(formatter_to_use) ||
(raise ArgumentError, "Formatter '#{formatter_to_use}' unknown - " \
"maybe you meant 'documentation' or 'progress'?.")
end
def register(formatter, notifications)
return if duplicate_formatter_exists?(formatter)
@reporter.register_listener formatter, *notifications
@formatters << formatter
formatter
end
def duplicate_formatter_exists?(new_formatter)
@formatters.any? do |formatter|
formatter.class == new_formatter.class &&
has_matching_output?(formatter, new_formatter)
end
end
def has_matching_output?(formatter, new_formatter)
return true unless formatter.respond_to?(:output) && new_formatter.respond_to?(:output)
formatter.output == new_formatter.output
end
def existing_formatter_implements?(notification)
@reporter.registered_listeners(notification).any?
end
def built_in_formatter(key)
case key.to_s
when 'd', 'doc', 'documentation'
DocumentationFormatter
when 'h', 'html'
HtmlFormatter
when 'p', 'progress'
ProgressFormatter
when 'j', 'json'
JsonFormatter
when 'bisect-drb'
BisectDRbFormatter
when 'f', 'failures'
FailureListFormatter
end
end
def notifications_for(formatter_class)
formatter_class.ancestors.inject(::RSpec::Core::Set.new) do |notifications, klass|
notifications.merge Loader.formatters.fetch(klass) { ::RSpec::Core::Set.new }
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) { |a, e| a.const_get e }
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 open_stream(path_or_wrapper)
if RSpec::Core::OutputWrapper === path_or_wrapper
path_or_wrapper.output = open_stream(path_or_wrapper.output)
path_or_wrapper
else
RSpec::Support::DirectoryMaker.mkdir_p(File.dirname(path_or_wrapper))
File.new(path_or_wrapper, 'w')
end
end
end
end
|