rspec-expectations 3.8.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +5 -0
- data/.document +5 -0
- data/.yardopts +6 -0
- data/Changelog.md +1156 -0
- data/LICENSE.md +25 -0
- data/README.md +305 -0
- data/lib/rspec/expectations.rb +82 -0
- data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
- data/lib/rspec/expectations/configuration.rb +215 -0
- data/lib/rspec/expectations/expectation_target.rb +127 -0
- data/lib/rspec/expectations/fail_with.rb +39 -0
- data/lib/rspec/expectations/failure_aggregator.rb +194 -0
- data/lib/rspec/expectations/handler.rb +170 -0
- data/lib/rspec/expectations/minitest_integration.rb +58 -0
- data/lib/rspec/expectations/syntax.rb +132 -0
- data/lib/rspec/expectations/version.rb +8 -0
- data/lib/rspec/matchers.rb +1034 -0
- data/lib/rspec/matchers/aliased_matcher.rb +116 -0
- data/lib/rspec/matchers/built_in.rb +52 -0
- data/lib/rspec/matchers/built_in/all.rb +86 -0
- data/lib/rspec/matchers/built_in/base_matcher.rb +193 -0
- data/lib/rspec/matchers/built_in/be.rb +288 -0
- data/lib/rspec/matchers/built_in/be_between.rb +77 -0
- data/lib/rspec/matchers/built_in/be_instance_of.rb +26 -0
- data/lib/rspec/matchers/built_in/be_kind_of.rb +20 -0
- data/lib/rspec/matchers/built_in/be_within.rb +72 -0
- data/lib/rspec/matchers/built_in/change.rb +428 -0
- data/lib/rspec/matchers/built_in/compound.rb +271 -0
- data/lib/rspec/matchers/built_in/contain_exactly.rb +302 -0
- data/lib/rspec/matchers/built_in/cover.rb +24 -0
- data/lib/rspec/matchers/built_in/eq.rb +40 -0
- data/lib/rspec/matchers/built_in/eql.rb +34 -0
- data/lib/rspec/matchers/built_in/equal.rb +81 -0
- data/lib/rspec/matchers/built_in/exist.rb +90 -0
- data/lib/rspec/matchers/built_in/has.rb +103 -0
- data/lib/rspec/matchers/built_in/have_attributes.rb +114 -0
- data/lib/rspec/matchers/built_in/include.rb +149 -0
- data/lib/rspec/matchers/built_in/match.rb +106 -0
- data/lib/rspec/matchers/built_in/operators.rb +128 -0
- data/lib/rspec/matchers/built_in/output.rb +200 -0
- data/lib/rspec/matchers/built_in/raise_error.rb +230 -0
- data/lib/rspec/matchers/built_in/respond_to.rb +165 -0
- data/lib/rspec/matchers/built_in/satisfy.rb +60 -0
- data/lib/rspec/matchers/built_in/start_or_end_with.rb +94 -0
- data/lib/rspec/matchers/built_in/throw_symbol.rb +132 -0
- data/lib/rspec/matchers/built_in/yield.rb +432 -0
- data/lib/rspec/matchers/composable.rb +171 -0
- data/lib/rspec/matchers/dsl.rb +527 -0
- data/lib/rspec/matchers/english_phrasing.rb +58 -0
- data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +73 -0
- data/lib/rspec/matchers/fail_matchers.rb +42 -0
- data/lib/rspec/matchers/generated_descriptions.rb +41 -0
- data/lib/rspec/matchers/matcher_delegator.rb +35 -0
- data/lib/rspec/matchers/matcher_protocol.rb +99 -0
- metadata +215 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,215 @@
|
|
1
|
+
RSpec::Support.require_rspec_expectations "syntax"
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module Expectations
|
5
|
+
# Provides configuration options for rspec-expectations.
|
6
|
+
# If you are using rspec-core, you can access this via a
|
7
|
+
# block passed to `RSpec::Core::Configuration#expect_with`.
|
8
|
+
# Otherwise, you can access it via RSpec::Expectations.configuration.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# RSpec.configure do |rspec|
|
12
|
+
# rspec.expect_with :rspec do |c|
|
13
|
+
# # c is the config object
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# # or
|
18
|
+
#
|
19
|
+
# RSpec::Expectations.configuration
|
20
|
+
class Configuration
|
21
|
+
# @private
|
22
|
+
FALSE_POSITIVE_BEHAVIOURS =
|
23
|
+
{
|
24
|
+
:warn => lambda { |message| RSpec.warning message },
|
25
|
+
:raise => lambda { |message| raise ArgumentError, message },
|
26
|
+
:nothing => lambda { |_| true },
|
27
|
+
}
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@on_potential_false_positives = :warn
|
31
|
+
end
|
32
|
+
|
33
|
+
# Configures the supported syntax.
|
34
|
+
# @param [Array<Symbol>, Symbol] values the syntaxes to enable
|
35
|
+
# @example
|
36
|
+
# RSpec.configure do |rspec|
|
37
|
+
# rspec.expect_with :rspec do |c|
|
38
|
+
# c.syntax = :should
|
39
|
+
# # or
|
40
|
+
# c.syntax = :expect
|
41
|
+
# # or
|
42
|
+
# c.syntax = [:should, :expect]
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
def syntax=(values)
|
46
|
+
if Array(values).include?(:expect)
|
47
|
+
Expectations::Syntax.enable_expect
|
48
|
+
else
|
49
|
+
Expectations::Syntax.disable_expect
|
50
|
+
end
|
51
|
+
|
52
|
+
if Array(values).include?(:should)
|
53
|
+
Expectations::Syntax.enable_should
|
54
|
+
else
|
55
|
+
Expectations::Syntax.disable_should
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Configures the maximum character length that RSpec will print while
|
60
|
+
# formatting an object. You can set length to nil to prevent RSpec from
|
61
|
+
# doing truncation.
|
62
|
+
# @param [Fixnum] length the number of characters to limit the formatted output to.
|
63
|
+
# @example
|
64
|
+
# RSpec.configure do |rspec|
|
65
|
+
# rspec.expect_with :rspec do |c|
|
66
|
+
# c.max_formatted_output_length = 200
|
67
|
+
# end
|
68
|
+
# end
|
69
|
+
def max_formatted_output_length=(length)
|
70
|
+
RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length = length
|
71
|
+
end
|
72
|
+
|
73
|
+
# The list of configured syntaxes.
|
74
|
+
# @return [Array<Symbol>] the list of configured syntaxes.
|
75
|
+
# @example
|
76
|
+
# unless RSpec::Matchers.configuration.syntax.include?(:expect)
|
77
|
+
# raise "this RSpec extension gem requires the rspec-expectations `:expect` syntax"
|
78
|
+
# end
|
79
|
+
def syntax
|
80
|
+
syntaxes = []
|
81
|
+
syntaxes << :should if Expectations::Syntax.should_enabled?
|
82
|
+
syntaxes << :expect if Expectations::Syntax.expect_enabled?
|
83
|
+
syntaxes
|
84
|
+
end
|
85
|
+
|
86
|
+
if ::RSpec.respond_to?(:configuration)
|
87
|
+
def color?
|
88
|
+
::RSpec.configuration.color_enabled?
|
89
|
+
end
|
90
|
+
else
|
91
|
+
# Indicates whether or not diffs should be colored.
|
92
|
+
# Delegates to rspec-core's color option if rspec-core
|
93
|
+
# is loaded; otherwise you can set it here.
|
94
|
+
attr_writer :color
|
95
|
+
|
96
|
+
# Indicates whether or not diffs should be colored.
|
97
|
+
# Delegates to rspec-core's color option if rspec-core
|
98
|
+
# is loaded; otherwise you can set it here.
|
99
|
+
def color?
|
100
|
+
defined?(@color) && @color
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Adds `should` and `should_not` to the given classes
|
105
|
+
# or modules. This can be used to ensure `should` works
|
106
|
+
# properly on things like proxy objects (particular
|
107
|
+
# `Delegator`-subclassed objects on 1.8).
|
108
|
+
#
|
109
|
+
# @param [Array<Module>] modules the list of classes or modules
|
110
|
+
# to add `should` and `should_not` to.
|
111
|
+
def add_should_and_should_not_to(*modules)
|
112
|
+
modules.each do |mod|
|
113
|
+
Expectations::Syntax.enable_should(mod)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Sets or gets the backtrace formatter. The backtrace formatter should
|
118
|
+
# implement `#format_backtrace(Array<String>)`. This is used
|
119
|
+
# to format backtraces of errors handled by the `raise_error`
|
120
|
+
# matcher.
|
121
|
+
#
|
122
|
+
# If you are using rspec-core, rspec-core's backtrace formatting
|
123
|
+
# will be used (including respecting the presence or absence of
|
124
|
+
# the `--backtrace` option).
|
125
|
+
#
|
126
|
+
# @!attribute [rw] backtrace_formatter
|
127
|
+
attr_writer :backtrace_formatter
|
128
|
+
def backtrace_formatter
|
129
|
+
@backtrace_formatter ||= if defined?(::RSpec.configuration.backtrace_formatter)
|
130
|
+
::RSpec.configuration.backtrace_formatter
|
131
|
+
else
|
132
|
+
NullBacktraceFormatter
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Sets if custom matcher descriptions and failure messages
|
137
|
+
# should include clauses from methods defined using `chain`.
|
138
|
+
# @param value [Boolean]
|
139
|
+
attr_writer :include_chain_clauses_in_custom_matcher_descriptions
|
140
|
+
|
141
|
+
# Indicates whether or not custom matcher descriptions and failure messages
|
142
|
+
# should include clauses from methods defined using `chain`. It is
|
143
|
+
# false by default for backwards compatibility.
|
144
|
+
def include_chain_clauses_in_custom_matcher_descriptions?
|
145
|
+
@include_chain_clauses_in_custom_matcher_descriptions ||= false
|
146
|
+
end
|
147
|
+
|
148
|
+
# @private
|
149
|
+
def reset_syntaxes_to_default
|
150
|
+
self.syntax = [:should, :expect]
|
151
|
+
RSpec::Expectations::Syntax.warn_about_should!
|
152
|
+
end
|
153
|
+
|
154
|
+
# @api private
|
155
|
+
# Null implementation of a backtrace formatter used by default
|
156
|
+
# when rspec-core is not loaded. Does no filtering.
|
157
|
+
NullBacktraceFormatter = Module.new do
|
158
|
+
def self.format_backtrace(backtrace)
|
159
|
+
backtrace
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Configures whether RSpec will warn about matcher use which will
|
164
|
+
# potentially cause false positives in tests.
|
165
|
+
#
|
166
|
+
# @param [Boolean] boolean
|
167
|
+
def warn_about_potential_false_positives=(boolean)
|
168
|
+
if boolean
|
169
|
+
self.on_potential_false_positives = :warn
|
170
|
+
elsif warn_about_potential_false_positives?
|
171
|
+
self.on_potential_false_positives = :nothing
|
172
|
+
else
|
173
|
+
# no-op, handler is something else
|
174
|
+
end
|
175
|
+
end
|
176
|
+
#
|
177
|
+
# Configures what RSpec will do about matcher use which will
|
178
|
+
# potentially cause false positives in tests.
|
179
|
+
#
|
180
|
+
# @param [Symbol] behavior can be set to :warn, :raise or :nothing
|
181
|
+
def on_potential_false_positives=(behavior)
|
182
|
+
unless FALSE_POSITIVE_BEHAVIOURS.key?(behavior)
|
183
|
+
raise ArgumentError, "Supported values are: #{FALSE_POSITIVE_BEHAVIOURS.keys}"
|
184
|
+
end
|
185
|
+
@on_potential_false_positives = behavior
|
186
|
+
end
|
187
|
+
|
188
|
+
# Indicates what RSpec will do about matcher use which will
|
189
|
+
# potentially cause false positives in tests, generally you want to
|
190
|
+
# avoid such scenarios so this defaults to `true`.
|
191
|
+
attr_reader :on_potential_false_positives
|
192
|
+
|
193
|
+
# Indicates whether RSpec will warn about matcher use which will
|
194
|
+
# potentially cause false positives in tests, generally you want to
|
195
|
+
# avoid such scenarios so this defaults to `true`.
|
196
|
+
def warn_about_potential_false_positives?
|
197
|
+
on_potential_false_positives == :warn
|
198
|
+
end
|
199
|
+
|
200
|
+
# @private
|
201
|
+
def false_positives_handler
|
202
|
+
FALSE_POSITIVE_BEHAVIOURS.fetch(@on_potential_false_positives)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# The configuration object.
|
207
|
+
# @return [RSpec::Expectations::Configuration] the configuration object
|
208
|
+
def self.configuration
|
209
|
+
@configuration ||= Configuration.new
|
210
|
+
end
|
211
|
+
|
212
|
+
# set default syntax
|
213
|
+
configuration.reset_syntaxes_to_default
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Expectations
|
3
|
+
# Wraps the target of an expectation.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# expect(something) # => ExpectationTarget wrapping something
|
7
|
+
# expect { do_something } # => ExpectationTarget wrapping the block
|
8
|
+
#
|
9
|
+
# # used with `to`
|
10
|
+
# expect(actual).to eq(3)
|
11
|
+
#
|
12
|
+
# # with `not_to`
|
13
|
+
# expect(actual).not_to eq(3)
|
14
|
+
#
|
15
|
+
# @note `ExpectationTarget` is not intended to be instantiated
|
16
|
+
# directly by users. Use `expect` instead.
|
17
|
+
class ExpectationTarget
|
18
|
+
# @private
|
19
|
+
# Used as a sentinel value to be able to tell when the user
|
20
|
+
# did not pass an argument. We can't use `nil` for that because
|
21
|
+
# `nil` is a valid value to pass.
|
22
|
+
UndefinedValue = Module.new
|
23
|
+
|
24
|
+
# @note this name aligns with `Minitest::Expectation` so that our
|
25
|
+
# {InstanceMethods} module can be included in that class when
|
26
|
+
# used in a Minitest context.
|
27
|
+
# @return [Object] the target of the expectation
|
28
|
+
attr_reader :target
|
29
|
+
|
30
|
+
# @api private
|
31
|
+
def initialize(value)
|
32
|
+
@target = value
|
33
|
+
end
|
34
|
+
|
35
|
+
# @private
|
36
|
+
def self.for(value, block)
|
37
|
+
if UndefinedValue.equal?(value)
|
38
|
+
unless block
|
39
|
+
raise ArgumentError, "You must pass either an argument or a block to `expect`."
|
40
|
+
end
|
41
|
+
BlockExpectationTarget.new(block)
|
42
|
+
elsif block
|
43
|
+
raise ArgumentError, "You cannot pass both an argument and a block to `expect`."
|
44
|
+
else
|
45
|
+
new(value)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Defines instance {ExpectationTarget} instance methods. These are defined
|
50
|
+
# in a module so we can include it in `Minitest::Expectation` when
|
51
|
+
# `rspec/expectations/minitest_integration` is loaded in order to
|
52
|
+
# support usage with Minitest.
|
53
|
+
module InstanceMethods
|
54
|
+
# Runs the given expectation, passing if `matcher` returns true.
|
55
|
+
# @example
|
56
|
+
# expect(value).to eq(5)
|
57
|
+
# expect { perform }.to raise_error
|
58
|
+
# @param [Matcher]
|
59
|
+
# matcher
|
60
|
+
# @param [String or Proc] message optional message to display when the expectation fails
|
61
|
+
# @return [Boolean] true if the expectation succeeds (else raises)
|
62
|
+
# @see RSpec::Matchers
|
63
|
+
def to(matcher=nil, message=nil, &block)
|
64
|
+
prevent_operator_matchers(:to) unless matcher
|
65
|
+
RSpec::Expectations::PositiveExpectationHandler.handle_matcher(target, matcher, message, &block)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Runs the given expectation, passing if `matcher` returns false.
|
69
|
+
# @example
|
70
|
+
# expect(value).not_to eq(5)
|
71
|
+
# @param [Matcher]
|
72
|
+
# matcher
|
73
|
+
# @param [String or Proc] message optional message to display when the expectation fails
|
74
|
+
# @return [Boolean] false if the negative expectation succeeds (else raises)
|
75
|
+
# @see RSpec::Matchers
|
76
|
+
def not_to(matcher=nil, message=nil, &block)
|
77
|
+
prevent_operator_matchers(:not_to) unless matcher
|
78
|
+
RSpec::Expectations::NegativeExpectationHandler.handle_matcher(target, matcher, message, &block)
|
79
|
+
end
|
80
|
+
alias to_not not_to
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def prevent_operator_matchers(verb)
|
85
|
+
raise ArgumentError, "The expect syntax does not support operator matchers, " \
|
86
|
+
"so you must pass a matcher to `##{verb}`."
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
include InstanceMethods
|
91
|
+
end
|
92
|
+
|
93
|
+
# @private
|
94
|
+
# Validates the provided matcher to ensure it supports block
|
95
|
+
# expectations, in order to avoid user confusion when they
|
96
|
+
# use a block thinking the expectation will be on the return
|
97
|
+
# value of the block rather than the block itself.
|
98
|
+
class BlockExpectationTarget < ExpectationTarget
|
99
|
+
def to(matcher, message=nil, &block)
|
100
|
+
enforce_block_expectation(matcher)
|
101
|
+
super
|
102
|
+
end
|
103
|
+
|
104
|
+
def not_to(matcher, message=nil, &block)
|
105
|
+
enforce_block_expectation(matcher)
|
106
|
+
super
|
107
|
+
end
|
108
|
+
alias to_not not_to
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def enforce_block_expectation(matcher)
|
113
|
+
return if supports_block_expectations?(matcher)
|
114
|
+
|
115
|
+
raise ExpectationNotMetError, "You must pass an argument rather than a block to `expect` to use the provided " \
|
116
|
+
"matcher (#{RSpec::Support::ObjectFormatter.format(matcher)}), or the matcher must implement " \
|
117
|
+
"`supports_block_expectations?`."
|
118
|
+
end
|
119
|
+
|
120
|
+
def supports_block_expectations?(matcher)
|
121
|
+
matcher.supports_block_expectations?
|
122
|
+
rescue NoMethodError
|
123
|
+
false
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Expectations
|
3
|
+
class << self
|
4
|
+
# @private
|
5
|
+
class Differ
|
6
|
+
# @private
|
7
|
+
OBJECT_PREPARER = lambda do |object|
|
8
|
+
RSpec::Matchers::Composable.surface_descriptions_in(object)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# @private
|
13
|
+
def differ
|
14
|
+
RSpec::Support::Differ.new(
|
15
|
+
:object_preparer => Differ::OBJECT_PREPARER,
|
16
|
+
:color => RSpec::Matchers.configuration.color?
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Raises an RSpec::Expectations::ExpectationNotMetError with message.
|
21
|
+
# @param [String] message
|
22
|
+
# @param [Object] expected
|
23
|
+
# @param [Object] actual
|
24
|
+
#
|
25
|
+
# Adds a diff to the failure message when `expected` and `actual` are
|
26
|
+
# both present.
|
27
|
+
def fail_with(message, expected=nil, actual=nil)
|
28
|
+
unless message
|
29
|
+
raise ArgumentError, "Failure message is nil. Does your matcher define the " \
|
30
|
+
"appropriate failure_message[_when_negated] method to return a string?"
|
31
|
+
end
|
32
|
+
|
33
|
+
message = ::RSpec::Matchers::ExpectedsForMultipleDiffs.from(expected).message_with_diff(message, differ, actual)
|
34
|
+
|
35
|
+
RSpec::Support.notify_failure(RSpec::Expectations::ExpectationNotMetError.new message)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Expectations
|
3
|
+
# @private
|
4
|
+
class FailureAggregator
|
5
|
+
attr_reader :block_label, :metadata
|
6
|
+
|
7
|
+
def aggregate
|
8
|
+
RSpec::Support.with_failure_notifier(self) do
|
9
|
+
begin
|
10
|
+
yield
|
11
|
+
rescue ExpectationNotMetError => e
|
12
|
+
# Normally, expectation failures will be notified via the `call` method, below,
|
13
|
+
# but since the failure notifier uses a thread local variable, failing expectations
|
14
|
+
# in another thread will still raise. We handle that here and categorize it as part
|
15
|
+
# of `failures` rather than letting it fall through and be categorized as part of
|
16
|
+
# `other_errors`.
|
17
|
+
failures << e
|
18
|
+
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e
|
19
|
+
# While it is normally a bad practice to rescue `Exception`, it's important we do
|
20
|
+
# so here. It's low risk (`notify_aggregated_failures` below will re-raise the exception,
|
21
|
+
# or raise a `MultipleExpectationsNotMetError` that includes the exception), and it's
|
22
|
+
# essential that the user is notified of expectation failures that may have already
|
23
|
+
# occurred in the `aggregate_failures` block. Those expectation failures may provide
|
24
|
+
# important diagnostics for understanding why this exception occurred, and if we simply
|
25
|
+
# allowed this exception to be raised as-is, it would (wrongly) suggest to the user
|
26
|
+
# that the expectation passed when it did not, which would be quite confusing.
|
27
|
+
other_errors << e
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
notify_aggregated_failures
|
32
|
+
end
|
33
|
+
|
34
|
+
def failures
|
35
|
+
@failures ||= []
|
36
|
+
end
|
37
|
+
|
38
|
+
def other_errors
|
39
|
+
@other_errors ||= []
|
40
|
+
end
|
41
|
+
|
42
|
+
# This method is defined to satisfy the callable interface
|
43
|
+
# expected by `RSpec::Support.with_failure_notifier`.
|
44
|
+
def call(failure, options)
|
45
|
+
source_id = options[:source_id]
|
46
|
+
return if source_id && @seen_source_ids.key?(source_id)
|
47
|
+
|
48
|
+
@seen_source_ids[source_id] = true
|
49
|
+
assign_backtrace(failure) unless failure.backtrace
|
50
|
+
failures << failure
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
if RSpec::Support::Ruby.jruby?
|
56
|
+
# On JRuby, `caller` and `raise` produce different backtraces with regards to `.java`
|
57
|
+
# stack frames. It's important that we use `raise` for JRuby to produce a backtrace
|
58
|
+
# that has a continuous common section with the raised `MultipleExpectationsNotMetError`,
|
59
|
+
# so that rspec-core's truncation logic can work properly on it to list the backtrace
|
60
|
+
# relative to the `aggregate_failures` block.
|
61
|
+
def assign_backtrace(failure)
|
62
|
+
raise failure
|
63
|
+
rescue failure.class => e
|
64
|
+
failure.set_backtrace(e.backtrace)
|
65
|
+
end
|
66
|
+
else
|
67
|
+
# Using `caller` performs better (and is simpler) than `raise` on most Rubies.
|
68
|
+
def assign_backtrace(failure)
|
69
|
+
failure.set_backtrace(caller)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize(block_label, metadata)
|
74
|
+
@block_label = block_label
|
75
|
+
@metadata = metadata
|
76
|
+
@seen_source_ids = {} # don't want to load stdlib set
|
77
|
+
end
|
78
|
+
|
79
|
+
def notify_aggregated_failures
|
80
|
+
all_errors = failures + other_errors
|
81
|
+
|
82
|
+
case all_errors.size
|
83
|
+
when 0 then return nil
|
84
|
+
when 1 then RSpec::Support.notify_failure all_errors.first
|
85
|
+
else RSpec::Support.notify_failure MultipleExpectationsNotMetError.new(self)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Exception raised from `aggregate_failures` when multiple expectations fail.
|
91
|
+
class MultipleExpectationsNotMetError
|
92
|
+
# @return [String] The fully formatted exception message.
|
93
|
+
def message
|
94
|
+
@message ||= (["#{summary}:"] + enumerated_failures + enumerated_errors).join("\n\n")
|
95
|
+
end
|
96
|
+
|
97
|
+
# @return [Array<RSpec::Expectations::ExpectationNotMetError>] The list of expectation failures.
|
98
|
+
def failures
|
99
|
+
@failure_aggregator.failures
|
100
|
+
end
|
101
|
+
|
102
|
+
# @return [Array<Exception>] The list of other exceptions.
|
103
|
+
def other_errors
|
104
|
+
@failure_aggregator.other_errors
|
105
|
+
end
|
106
|
+
|
107
|
+
# @return [Array<Exception>] The list of expectation failures and other exceptions, combined.
|
108
|
+
attr_reader :all_exceptions
|
109
|
+
|
110
|
+
# @return [String] The user-assigned label for the aggregation block.
|
111
|
+
def aggregation_block_label
|
112
|
+
@failure_aggregator.block_label
|
113
|
+
end
|
114
|
+
|
115
|
+
# @return [Hash] The metadata hash passed to `aggregate_failures`.
|
116
|
+
def aggregation_metadata
|
117
|
+
@failure_aggregator.metadata
|
118
|
+
end
|
119
|
+
|
120
|
+
# @return [String] A summary of the failure, including the block label and a count of failures.
|
121
|
+
def summary
|
122
|
+
"Got #{exception_count_description} from failure aggregation " \
|
123
|
+
"block#{block_description}"
|
124
|
+
end
|
125
|
+
|
126
|
+
# return [String] A description of the failure/error counts.
|
127
|
+
def exception_count_description
|
128
|
+
failure_count = pluralize("failure", failures.size)
|
129
|
+
return failure_count if other_errors.empty?
|
130
|
+
error_count = pluralize("other error", other_errors.size)
|
131
|
+
"#{failure_count} and #{error_count}"
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
def initialize(failure_aggregator)
|
137
|
+
@failure_aggregator = failure_aggregator
|
138
|
+
@all_exceptions = failures + other_errors
|
139
|
+
end
|
140
|
+
|
141
|
+
def block_description
|
142
|
+
return "" unless aggregation_block_label
|
143
|
+
" #{aggregation_block_label.inspect}"
|
144
|
+
end
|
145
|
+
|
146
|
+
def pluralize(noun, count)
|
147
|
+
"#{count} #{noun}#{'s' unless count == 1}"
|
148
|
+
end
|
149
|
+
|
150
|
+
def enumerated(exceptions, index_offset)
|
151
|
+
exceptions.each_with_index.map do |exception, index|
|
152
|
+
index += index_offset
|
153
|
+
formatted_message = yield exception
|
154
|
+
"#{index_label index}#{indented formatted_message, index}"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def enumerated_failures
|
159
|
+
enumerated(failures, 0, &:message)
|
160
|
+
end
|
161
|
+
|
162
|
+
def enumerated_errors
|
163
|
+
enumerated(other_errors, failures.size) do |error|
|
164
|
+
"#{error.class}: #{error.message}"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def indented(failure_message, index)
|
169
|
+
line_1, *rest = failure_message.strip.lines.to_a
|
170
|
+
first_line_indentation = ' ' * (longest_index_label_width - width_of_label(index))
|
171
|
+
|
172
|
+
first_line_indentation + line_1 + rest.map do |line|
|
173
|
+
line =~ /\S/ ? indentation + line : line
|
174
|
+
end.join
|
175
|
+
end
|
176
|
+
|
177
|
+
def indentation
|
178
|
+
@indentation ||= ' ' * longest_index_label_width
|
179
|
+
end
|
180
|
+
|
181
|
+
def longest_index_label_width
|
182
|
+
@longest_index_label_width ||= width_of_label(failures.size)
|
183
|
+
end
|
184
|
+
|
185
|
+
def width_of_label(index)
|
186
|
+
index_label(index).chars.count
|
187
|
+
end
|
188
|
+
|
189
|
+
def index_label(index)
|
190
|
+
" #{index + 1}) "
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|