rspec-expectations 3.6.0.beta2 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4334ca25737c438a7a9266a95fb2cd4182853299
4
- data.tar.gz: 810d8162263ea539f45652f84c0bf9deec512e6b
3
+ metadata.gz: aa835825e71953a32260e80c58d892f5517ed692
4
+ data.tar.gz: 3e66073a9dbefb71be893d00692df5162826105f
5
5
  SHA512:
6
- metadata.gz: b9d714c6f1f8ee892a133df714e7ffcc5afca4aa0f73735eda1478591a4aeebe3601c61d96d1ceab68c60e9f023b1f629107619ecd7150a74efa7c5ce276992d
7
- data.tar.gz: 45c51f595123a44b52c02058939248bf53eb3d73e7e6305a79936250d31208e191817ff60862856accc5f34e0bc5090204617d122e9cb0dd1841143976a21237
6
+ metadata.gz: e967e93899006e4a6c9b419b217f6cac32aee74d60d006724db428e4d574fea2be76499759348808e50f9ba69a7e34d9472aa02eb7b23cca04e56ef9beab6743
7
+ data.tar.gz: 96f5ea4360e26175df7dfaae04a9f76c0d1d273ccca0e8939722947e59c7d09fd3a0c3e29eb27035101060cd80d540b90cfb6abcb4b7a40d60d5f8ae5a307a16
checksums.yaml.gz.sig CHANGED
Binary file
data.tar.gz.sig CHANGED
Binary file
data/Changelog.md CHANGED
@@ -1,3 +1,23 @@
1
+ ### 3.6.0 / 2017-05-04
2
+ [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.6.0.beta2...v3.6.0)
3
+
4
+ Enhancements:
5
+
6
+ * Treat NoMethodError as a failure for comparison matchers. (Jon Rowe, #972)
7
+ * Allow for scoped aliased and negated matchers--just call
8
+ `alias_matcher` or `define_negated_matcher` from within an example
9
+ group. (Markus Reiter, #974)
10
+ * Improve failure message of `change` matcher with block and `satisfy` matcher
11
+ by including the block snippet instead of just describing it as `result` or
12
+ `block` when Ripper is available. (Yuji Nakayama, #987)
13
+
14
+ Bug Fixes:
15
+
16
+ * Fix `yield_with_args` and `yield_successive_args` matchers so that
17
+ they compare expected to actual args at the time the args are yielded
18
+ instead of at the end, in case the method that is yielding mutates the
19
+ arguments after yielding. (Alyssa Ross, #965)
20
+
1
21
  ### 3.6.0.beta2 / 2016-12-12
2
22
  [Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.6.0.beta1...v3.6.0.beta2)
3
23
 
@@ -92,7 +112,7 @@ Bug Fixes:
92
112
 
93
113
  * Fix failure message from dynamic predicate matchers when the object
94
114
  does not respond to the predicate so that it is inspected rather
95
- than relying upon it's `to_s` -- that way for `nil`, `"nil"` is
115
+ than relying upon its `to_s` -- that way for `nil`, `"nil"` is
96
116
  printed rather than an empty string. (Myron Marston, #841)
97
117
  * Fix SystemStackError raised when diffing an Enumerable object
98
118
  whose `#each` includes the object itself. (Yuji Nakayama, #857)
data/README.md CHANGED
@@ -3,7 +3,9 @@
3
3
  RSpec::Expectations lets you express expected outcomes on an object in an
4
4
  example.
5
5
 
6
- expect(account.balance).to eq(Money.new(37.42, :USD))
6
+ ```ruby
7
+ expect(account.balance).to eq(Money.new(37.42, :USD))
8
+ ```
7
9
 
8
10
  ## Install
9
11
 
@@ -150,7 +152,7 @@ expect { |b| 5.tap(&b) }.to yield_control # passes regardless of yielded args
150
152
  expect { |b| yield_if_true(true, &b) }.to yield_with_no_args # passes only if no args are yielded
151
153
 
152
154
  expect { |b| 5.tap(&b) }.to yield_with_args(5)
153
- expect { |b| 5.tap(&b) }.to yield_with_args(Fixnum)
155
+ expect { |b| 5.tap(&b) }.to yield_with_args(Integer)
154
156
  expect { |b| "a string".tap(&b) }.to yield_with_args(/str/)
155
157
 
156
158
  expect { |b| [1, 2, 3].each(&b) }.to yield_successive_args(1, 2, 3)
@@ -263,7 +265,7 @@ expect(hash).to match(
263
265
  :a => {
264
266
  :b => a_collection_containing_exactly(
265
267
  a_string_starting_with("f"),
266
- an_instance_of(Fixnum)
268
+ an_instance_of(Integer)
267
269
  ),
268
270
  :c => { :d => (a_value < 3) }
269
271
  }
@@ -76,6 +76,7 @@ module RSpec
76
76
  class MultipleExpectationsNotMetError < ExpectationNotMetError
77
77
  end
78
78
 
79
- autoload :FailureAggregator, "rspec/expectations/failure_aggregator"
79
+ autoload :BlockSnippetExtractor, "rspec/expectations/block_snippet_extractor"
80
+ autoload :FailureAggregator, "rspec/expectations/failure_aggregator"
80
81
  end
81
82
  end
@@ -0,0 +1,253 @@
1
+ module RSpec
2
+ module Expectations
3
+ # @private
4
+ class BlockSnippetExtractor # rubocop:disable Style/ClassLength
5
+ # rubocop should properly handle `Struct.new {}` as an inner class definition.
6
+
7
+ attr_reader :proc, :method_name
8
+
9
+ def self.try_extracting_single_line_body_of(proc, method_name)
10
+ lines = new(proc, method_name).body_content_lines
11
+ return nil unless lines.count == 1
12
+ lines.first
13
+ rescue Error
14
+ nil
15
+ end
16
+
17
+ def initialize(proc, method_name)
18
+ @proc = proc
19
+ @method_name = method_name.to_s.freeze
20
+ end
21
+
22
+ # Ideally we should properly handle indentations of multiline snippet,
23
+ # but it's not implemented yet since because we use result of this method only when it's a
24
+ # single line and implementing the logic introduces additional complexity.
25
+ def body_content_lines
26
+ raw_body_lines.map(&:strip).reject(&:empty?)
27
+ end
28
+
29
+ private
30
+
31
+ def raw_body_lines
32
+ raw_body_snippet.split("\n")
33
+ end
34
+
35
+ def raw_body_snippet
36
+ block_token_extractor.body_tokens.map(&:string).join
37
+ end
38
+
39
+ def block_token_extractor
40
+ @block_token_extractor ||= BlockTokenExtractor.new(method_name, source, beginning_line_number)
41
+ end
42
+
43
+ if RSpec.respond_to?(:world)
44
+ def source
45
+ raise TargetNotFoundError unless File.exist?(file_path)
46
+ RSpec.world.source_from_file(file_path)
47
+ end
48
+ else
49
+ RSpec::Support.require_rspec_support 'source'
50
+ def source
51
+ raise TargetNotFoundError unless File.exist?(file_path)
52
+ @source ||= RSpec::Support::Source.from_file(file_path)
53
+ end
54
+ end
55
+
56
+ def file_path
57
+ source_location.first
58
+ end
59
+
60
+ def beginning_line_number
61
+ source_location.last
62
+ end
63
+
64
+ def source_location
65
+ proc.source_location || raise(TargetNotFoundError)
66
+ end
67
+
68
+ Error = Class.new(StandardError)
69
+ TargetNotFoundError = Class.new(Error)
70
+ AmbiguousTargetError = Class.new(Error)
71
+
72
+ # @private
73
+ # Performs extraction of block body snippet using tokens,
74
+ # which cannot be done with node information.
75
+ BlockTokenExtractor = Struct.new(:method_name, :source, :beginning_line_number) do
76
+ attr_reader :state, :body_tokens
77
+
78
+ def initialize(*)
79
+ super
80
+ parse!
81
+ end
82
+
83
+ private
84
+
85
+ def parse!
86
+ @state = :initial
87
+
88
+ catch(:finish) do
89
+ source.tokens.each do |token|
90
+ invoke_state_handler(token)
91
+ end
92
+ end
93
+ end
94
+
95
+ def finish!
96
+ throw :finish
97
+ end
98
+
99
+ def invoke_state_handler(token)
100
+ __send__("#{state}_state", token)
101
+ end
102
+
103
+ def initial_state(token)
104
+ @state = :after_method_call if token.location == block_locator.method_call_location
105
+ end
106
+
107
+ def after_method_call_state(token)
108
+ @state = :after_opener if handle_opener_token(token)
109
+ end
110
+
111
+ def after_opener_state(token)
112
+ if handle_closer_token(token)
113
+ finish_or_find_next_block_if_incorrect!
114
+ elsif pipe_token?(token)
115
+ finalize_pending_tokens!
116
+ @state = :after_beginning_of_args
117
+ else
118
+ pending_tokens << token
119
+ handle_opener_token(token)
120
+ @state = :after_beginning_of_body unless token.type == :on_sp
121
+ end
122
+ end
123
+
124
+ def after_beginning_of_args_state(token)
125
+ @state = :after_beginning_of_body if pipe_token?(token)
126
+ end
127
+
128
+ def after_beginning_of_body_state(token)
129
+ if handle_closer_token(token)
130
+ finish_or_find_next_block_if_incorrect!
131
+ else
132
+ pending_tokens << token
133
+ handle_opener_token(token)
134
+ end
135
+ end
136
+
137
+ def pending_tokens
138
+ @pending_tokens ||= []
139
+ end
140
+
141
+ def finalize_pending_tokens!
142
+ pending_tokens.freeze.tap do
143
+ @pending_tokens = nil
144
+ end
145
+ end
146
+
147
+ def finish_or_find_next_block_if_incorrect!
148
+ body_tokens = finalize_pending_tokens!
149
+
150
+ if correct_block?(body_tokens)
151
+ @body_tokens = body_tokens
152
+ finish!
153
+ else
154
+ @state = :after_method_call
155
+ end
156
+ end
157
+
158
+ def handle_opener_token(token)
159
+ opener_token?(token).tap do |boolean|
160
+ opener_token_stack.push(token) if boolean
161
+ end
162
+ end
163
+
164
+ def opener_token?(token)
165
+ token.type == :on_lbrace || (token.type == :on_kw && token.string == 'do')
166
+ end
167
+
168
+ def handle_closer_token(token)
169
+ if opener_token_stack.last.closed_by?(token)
170
+ opener_token_stack.pop
171
+ opener_token_stack.empty?
172
+ else
173
+ false
174
+ end
175
+ end
176
+
177
+ def opener_token_stack
178
+ @opener_token_stack ||= []
179
+ end
180
+
181
+ def pipe_token?(token)
182
+ token.type == :on_op && token.string == '|'
183
+ end
184
+
185
+ def correct_block?(body_tokens)
186
+ return true if block_locator.body_content_locations.empty?
187
+ content_location = block_locator.body_content_locations.first
188
+ content_location.between?(body_tokens.first.location, body_tokens.last.location)
189
+ end
190
+
191
+ def block_locator
192
+ @block_locator ||= BlockLocator.new(method_name, source, beginning_line_number)
193
+ end
194
+ end
195
+
196
+ # @private
197
+ # Locates target block with node information (semantics), which tokens don't have.
198
+ BlockLocator = Struct.new(:method_name, :source, :beginning_line_number) do
199
+ def method_call_location
200
+ @method_call_location ||= method_ident_node.location
201
+ end
202
+
203
+ def body_content_locations
204
+ @body_content_locations ||= block_body_node.map(&:location).compact
205
+ end
206
+
207
+ private
208
+
209
+ def method_ident_node
210
+ method_call_node = block_wrapper_node.children.first
211
+ method_call_node.find do |node|
212
+ method_ident_node?(node)
213
+ end
214
+ end
215
+
216
+ def block_body_node
217
+ block_node = block_wrapper_node.children[1]
218
+ block_node.children.last
219
+ end
220
+
221
+ def block_wrapper_node
222
+ case candidate_block_wrapper_nodes.size
223
+ when 1
224
+ candidate_block_wrapper_nodes.first
225
+ when 0
226
+ raise TargetNotFoundError
227
+ else
228
+ raise AmbiguousTargetError
229
+ end
230
+ end
231
+
232
+ def candidate_block_wrapper_nodes
233
+ @candidate_block_wrapper_nodes ||= candidate_method_ident_nodes.map do |method_ident_node|
234
+ block_wrapper_node = method_ident_node.each_ancestor.find { |node| node.type == :method_add_block }
235
+ next nil unless block_wrapper_node
236
+ method_call_node = block_wrapper_node.children.first
237
+ method_call_node.include?(method_ident_node) ? block_wrapper_node : nil
238
+ end.compact
239
+ end
240
+
241
+ def candidate_method_ident_nodes
242
+ source.nodes_by_line_number[beginning_line_number].select do |node|
243
+ method_ident_node?(node)
244
+ end
245
+ end
246
+
247
+ def method_ident_node?(node)
248
+ node.type == :@ident && node.args.first == method_name
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end
@@ -2,7 +2,7 @@ module RSpec
2
2
  module Expectations
3
3
  # @private
4
4
  module Version
5
- STRING = '3.6.0.beta2'
5
+ STRING = '3.6.0'
6
6
  end
7
7
  end
8
8
  end
@@ -41,9 +41,9 @@ module RSpec
41
41
  #
42
42
  # expect("a string").to be_an_instance_of(String) # =>"a string".instance_of?(String) # passes
43
43
  #
44
- # expect(3).to be_a_kind_of(Fixnum) # => 3.kind_of?(Numeric) | passes
45
- # expect(3).to be_a_kind_of(Numeric) # => 3.kind_of?(Numeric) | passes
46
- # expect(3).to be_an_instance_of(Fixnum) # => 3.instance_of?(Fixnum) | passes
44
+ # expect(3).to be_a_kind_of(Integer) # => 3.kind_of?(Numeric) | passes
45
+ # expect(3).to be_a_kind_of(Numeric) # => 3.kind_of?(Numeric) | passes
46
+ # expect(3).to be_an_instance_of(Integer) # => 3.instance_of?(Integer) | passes
47
47
  # expect(3).not_to be_an_instance_of(Numeric) # => 3.instance_of?(Numeric) | fails
48
48
  #
49
49
  # RSpec will also create custom matchers for predicates like `has_key?`. To
@@ -62,6 +62,26 @@ module RSpec
62
62
  # RSpec::Matchers.alias_matcher :a_user_who_is_an_admin, :be_an_admin
63
63
  # expect(user_list).to include(a_user_who_is_an_admin)
64
64
  #
65
+ # ## Alias Matchers
66
+ #
67
+ # With {RSpec::Matchers.alias_matcher}, you can easily create an
68
+ # alternate name for a given matcher.
69
+ #
70
+ # The description will also change according to the new name:
71
+ #
72
+ # RSpec::Matchers.alias_matcher :a_list_that_sums_to, :sum_to
73
+ # sum_to(3).description # => "sum to 3"
74
+ # a_list_that_sums_to(3).description # => "a list that sums to 3"
75
+ #
76
+ # or you can specify a custom description like this:
77
+ #
78
+ # RSpec::Matchers.alias_matcher :a_list_sorted_by, :be_sorted_by do |description|
79
+ # description.sub("be sorted by", "a list sorted by")
80
+ # end
81
+ #
82
+ # be_sorted_by(:age).description # => "be sorted by age"
83
+ # a_list_sorted_by(:age).description # => "a list sorted by age"
84
+ #
65
85
  # ## Custom Matchers
66
86
  #
67
87
  # When you find that none of the stock matchers provide a natural feeling
@@ -202,7 +222,34 @@ module RSpec
202
222
  # expressions, and also uses the noun-phrase wording in the matcher's `description`,
203
223
  # for readable failure messages. You can alias your custom matchers in similar fashion
204
224
  # using {RSpec::Matchers.alias_matcher}.
225
+ #
226
+ # ## Negated Matchers
227
+ #
228
+ # Sometimes if you want to test for the opposite using a more descriptive name
229
+ # instead of using `not_to`, you can use {RSpec::Matchers.define_negated_matcher}:
230
+ #
231
+ # RSpec::Matchers.define_negated_matcher :exclude, :include
232
+ # include(1, 2).description # => "include 1 and 2"
233
+ # exclude(1, 2).description # => "exclude 1 and 2"
234
+ #
235
+ # While the most obvious negated form may be to add a `not_` prefix,
236
+ # the failure messages you get with that form can be confusing (e.g.
237
+ # "expected [actual] to not [verb], but did not"). We've found it works
238
+ # best to find a more positive name for the negated form, such as
239
+ # `avoid_changing` rather than `not_change`.
240
+ #
205
241
  module Matchers
242
+ extend ::RSpec::Matchers::DSL
243
+
244
+ # @!method self.alias_matcher(new_name, old_name, options={}, &description_override)
245
+ # Extended from {RSpec::Matchers::DSL#alias_matcher}.
246
+
247
+ # @!method self.define(name, &declarations)
248
+ # Extended from {RSpec::Matchers::DSL#define}.
249
+
250
+ # @!method self.define_negated_matcher(negated_name, base_name, &description_override)
251
+ # Extended from {RSpec::Matchers::DSL#define_negated_matcher}.
252
+
206
253
  # @method expect
207
254
  # Supports `expect(actual).to matcher` syntax by wrapping `actual` in an
208
255
  # `ExpectationTarget`.
@@ -213,70 +260,6 @@ module RSpec
213
260
  # @see ExpectationTarget#to
214
261
  # @see ExpectationTarget#not_to
215
262
 
216
- # Defines a matcher alias. The returned matcher's `description` will be overriden
217
- # to reflect the phrasing of the new name, which will be used in failure messages
218
- # when passed as an argument to another matcher in a composed matcher expression.
219
- #
220
- # @param new_name [Symbol] the new name for the matcher
221
- # @param old_name [Symbol] the original name for the matcher
222
- # @param options [Hash] options for the aliased matcher
223
- # @option options [Class] :klass the ruby class to use as the decorator. (Not normally used).
224
- # @yield [String] optional block that, when given, is used to define the overriden
225
- # logic. The yielded arg is the original description or failure message. If no
226
- # block is provided, a default override is used based on the old and new names.
227
- #
228
- # @example
229
- # RSpec::Matchers.alias_matcher :a_list_that_sums_to, :sum_to
230
- # sum_to(3).description # => "sum to 3"
231
- # a_list_that_sums_to(3).description # => "a list that sums to 3"
232
- #
233
- # @example
234
- # RSpec::Matchers.alias_matcher :a_list_sorted_by, :be_sorted_by do |description|
235
- # description.sub("be sorted by", "a list sorted by")
236
- # end
237
- #
238
- # be_sorted_by(:age).description # => "be sorted by age"
239
- # a_list_sorted_by(:age).description # => "a list sorted by age"
240
- #
241
- # @!macro [attach] alias_matcher
242
- # @!parse
243
- # alias $1 $2
244
- def self.alias_matcher(new_name, old_name, options={}, &description_override)
245
- description_override ||= lambda do |old_desc|
246
- old_desc.gsub(EnglishPhrasing.split_words(old_name), EnglishPhrasing.split_words(new_name))
247
- end
248
- klass = options.fetch(:klass) { AliasedMatcher }
249
-
250
- define_method(new_name) do |*args, &block|
251
- matcher = __send__(old_name, *args, &block)
252
- klass.new(matcher, description_override)
253
- end
254
- end
255
-
256
- # Defines a negated matcher. The returned matcher's `description` and `failure_message`
257
- # will be overriden to reflect the phrasing of the new name, and the match logic will
258
- # be based on the original matcher but negated.
259
- #
260
- # @param negated_name [Symbol] the name for the negated matcher
261
- # @param base_name [Symbol] the name of the original matcher that will be negated
262
- # @yield [String] optional block that, when given, is used to define the overriden
263
- # logic. The yielded arg is the original description or failure message. If no
264
- # block is provided, a default override is used based on the old and new names.
265
- #
266
- # @example
267
- # RSpec::Matchers.define_negated_matcher :exclude, :include
268
- # include(1, 2).description # => "include 1 and 2"
269
- # exclude(1, 2).description # => "exclude 1 and 2"
270
- #
271
- # @note While the most obvious negated form may be to add a `not_` prefix,
272
- # the failure messages you get with that form can be confusing (e.g.
273
- # "expected [actual] to not [verb], but did not"). We've found it works
274
- # best to find a more positive name for the negated form, such as
275
- # `avoid_changing` rather than `not_change`.
276
- def self.define_negated_matcher(negated_name, base_name, &description_override)
277
- alias_matcher(negated_name, base_name, :klass => AliasedNegatedMatcher, &description_override)
278
- end
279
-
280
263
  # Allows multiple expectations in the provided block to fail, and then
281
264
  # aggregates them into a single exception, rather than aborting on the
282
265
  # first expectation failure like normal. This allows you to see all
@@ -313,6 +296,10 @@ module RSpec
313
296
  Expectations::FailureAggregator.new(label, metadata).aggregate(&block)
314
297
  end
315
298
 
299
+ # @!macro [attach] alias_matcher
300
+ # @!parse
301
+ # alias $1 $2
302
+
316
303
  # Passes if actual is truthy (anything but false or nil)
317
304
  def be_truthy
318
305
  BuiltIn::BeTruthy.new
@@ -367,7 +354,7 @@ module RSpec
367
354
  # Passes if actual.instance_of?(expected)
368
355
  #
369
356
  # @example
370
- # expect(5).to be_an_instance_of(Fixnum)
357
+ # expect(5).to be_an_instance_of(Integer)
371
358
  # expect(5).not_to be_an_instance_of(Numeric)
372
359
  # expect(5).not_to be_an_instance_of(Float)
373
360
  def be_an_instance_of(expected)
@@ -379,7 +366,7 @@ module RSpec
379
366
  # Passes if actual.kind_of?(expected)
380
367
  #
381
368
  # @example
382
- # expect(5).to be_a_kind_of(Fixnum)
369
+ # expect(5).to be_a_kind_of(Integer)
383
370
  # expect(5).to be_a_kind_of(Numeric)
384
371
  # expect(5).not_to be_a_kind_of(Float)
385
372
  def be_a_kind_of(expected)
@@ -585,7 +572,7 @@ module RSpec
585
572
  # information about equality in Ruby.
586
573
  #
587
574
  # @example
588
- # expect(5).to equal(5) # Fixnums are equal
575
+ # expect(5).to equal(5) # Integers are equal
589
576
  # expect("5").not_to equal("5") # Strings that look the same are not the same object
590
577
  def equal(expected)
591
578
  BuiltIn::Equal.new(expected)
@@ -688,7 +675,7 @@ module RSpec
688
675
  # :a => {
689
676
  # :b => a_collection_containing_exactly(
690
677
  # a_string_starting_with("f"),
691
- # an_instance_of(Fixnum)
678
+ # an_instance_of(Integer)
692
679
  # ),
693
680
  # :c => { :d => (a_value < 3) }
694
681
  # }
@@ -809,7 +796,7 @@ module RSpec
809
796
  # @example
810
797
  # expect(5).to satisfy { |n| n > 3 }
811
798
  # expect(5).to satisfy("be greater than 3") { |n| n > 3 }
812
- def satisfy(description="satisfy block", &block)
799
+ def satisfy(description=nil, &block)
813
800
  BuiltIn::Satisfy.new(description, &block)
814
801
  end
815
802
  alias_matcher :an_object_satisfying, :satisfy
@@ -905,7 +892,7 @@ module RSpec
905
892
  # @example
906
893
  # expect { |b| 5.tap(&b) }.to yield_with_args # because #tap yields an arg
907
894
  # expect { |b| 5.tap(&b) }.to yield_with_args(5) # because 5 == 5
908
- # expect { |b| 5.tap(&b) }.to yield_with_args(Fixnum) # because Fixnum === 5
895
+ # expect { |b| 5.tap(&b) }.to yield_with_args(Integer) # because Integer === 5
909
896
  # expect { |b| File.open("f.txt", &b) }.to yield_with_args(/txt/) # because /txt/ === "f.txt"
910
897
  #
911
898
  # expect { |b| User.transaction(&b) }.not_to yield_with_args # because it yields no args
@@ -22,6 +22,9 @@ module RSpec
22
22
  # @private
23
23
  attr_reader :actual, :expected, :rescued_exception
24
24
 
25
+ # @private
26
+ attr_writer :matcher_name
27
+
25
28
  def initialize(expected=UNDEFINED)
26
29
  @expected = expected unless UNDEFINED.equal?(expected)
27
30
  end
@@ -95,6 +98,15 @@ module RSpec
95
98
  @matcher_name ||= underscore(name.split('::').last)
96
99
  end
97
100
 
101
+ # @private
102
+ def matcher_name
103
+ if defined?(@matcher_name)
104
+ @matcher_name
105
+ else
106
+ self.class.matcher_name
107
+ end
108
+ end
109
+
98
110
  # @private
99
111
  # Borrowed from ActiveSupport.
100
112
  def self.underscore(camel_cased_word)
@@ -145,7 +145,7 @@ module RSpec
145
145
  def matches?(actual)
146
146
  @actual = actual
147
147
  @actual.__send__ @operator, @expected
148
- rescue ArgumentError
148
+ rescue ArgumentError, NoMethodError
149
149
  false
150
150
  end
151
151
 
@@ -8,7 +8,7 @@ module RSpec
8
8
  # @api public
9
9
  # Specifies the delta of the expected change.
10
10
  def by(expected_delta)
11
- ChangeRelatively.new(@change_details, expected_delta, :by) do |actual_delta|
11
+ ChangeRelatively.new(change_details, expected_delta, :by) do |actual_delta|
12
12
  values_match?(expected_delta, actual_delta)
13
13
  end
14
14
  end
@@ -16,7 +16,7 @@ module RSpec
16
16
  # @api public
17
17
  # Specifies a minimum delta of the expected change.
18
18
  def by_at_least(minimum)
19
- ChangeRelatively.new(@change_details, minimum, :by_at_least) do |actual_delta|
19
+ ChangeRelatively.new(change_details, minimum, :by_at_least) do |actual_delta|
20
20
  actual_delta >= minimum
21
21
  end
22
22
  end
@@ -24,7 +24,7 @@ module RSpec
24
24
  # @api public
25
25
  # Specifies a maximum delta of the expected change.
26
26
  def by_at_most(maximum)
27
- ChangeRelatively.new(@change_details, maximum, :by_at_most) do |actual_delta|
27
+ ChangeRelatively.new(change_details, maximum, :by_at_most) do |actual_delta|
28
28
  actual_delta <= maximum
29
29
  end
30
30
  end
@@ -32,13 +32,13 @@ module RSpec
32
32
  # @api public
33
33
  # Specifies the new value you expect.
34
34
  def to(value)
35
- ChangeToValue.new(@change_details, value)
35
+ ChangeToValue.new(change_details, value)
36
36
  end
37
37
 
38
38
  # @api public
39
39
  # Specifies the original value.
40
40
  def from(value)
41
- ChangeFromValue.new(@change_details, value)
41
+ ChangeFromValue.new(change_details, value)
42
42
  end
43
43
 
44
44
  # @private
@@ -46,8 +46,8 @@ module RSpec
46
46
  @event_proc = event_proc
47
47
  return false unless Proc === event_proc
48
48
  raise_block_syntax_error if block_given?
49
- @change_details.perform_change(event_proc)
50
- @change_details.changed?
49
+ change_details.perform_change(event_proc)
50
+ change_details.changed?
51
51
  end
52
52
 
53
53
  def does_not_match?(event_proc)
@@ -58,21 +58,21 @@ module RSpec
58
58
  # @api private
59
59
  # @return [String]
60
60
  def failure_message
61
- "expected #{@change_details.message} to have changed, " \
61
+ "expected #{change_details.value_representation} to have changed, " \
62
62
  "but #{positive_failure_reason}"
63
63
  end
64
64
 
65
65
  # @api private
66
66
  # @return [String]
67
67
  def failure_message_when_negated
68
- "expected #{@change_details.message} not to have changed, " \
68
+ "expected #{change_details.value_representation} not to have changed, " \
69
69
  "but #{negative_failure_reason}"
70
70
  end
71
71
 
72
72
  # @api private
73
73
  # @return [String]
74
74
  def description
75
- "change #{@change_details.message}"
75
+ "change #{change_details.value_representation}"
76
76
  end
77
77
 
78
78
  # @private
@@ -83,7 +83,13 @@ module RSpec
83
83
  private
84
84
 
85
85
  def initialize(receiver=nil, message=nil, &block)
86
- @change_details = ChangeDetails.new(receiver, message, &block)
86
+ @receiver = receiver
87
+ @message = message
88
+ @block = block
89
+ end
90
+
91
+ def change_details
92
+ @change_details ||= ChangeDetails.new(matcher_name, @receiver, @message, &@block)
87
93
  end
88
94
 
89
95
  def raise_block_syntax_error
@@ -93,13 +99,13 @@ module RSpec
93
99
 
94
100
  def positive_failure_reason
95
101
  return "was not given a block" unless Proc === @event_proc
96
- "is still #{description_of @change_details.actual_before}"
102
+ "is still #{description_of change_details.actual_before}"
97
103
  end
98
104
 
99
105
  def negative_failure_reason
100
106
  return "was not given a block" unless Proc === @event_proc
101
- "did change from #{description_of @change_details.actual_before} " \
102
- "to #{description_of @change_details.actual_after}"
107
+ "did change from #{description_of change_details.actual_before} " \
108
+ "to #{description_of change_details.actual_after}"
103
109
  end
104
110
  end
105
111
 
@@ -115,7 +121,7 @@ module RSpec
115
121
 
116
122
  # @private
117
123
  def failure_message
118
- "expected #{@change_details.message} to have changed " \
124
+ "expected #{@change_details.value_representation} to have changed " \
119
125
  "#{@relativity.to_s.tr('_', ' ')} " \
120
126
  "#{description_of @expected_delta}, but #{failure_reason}"
121
127
  end
@@ -136,7 +142,7 @@ module RSpec
136
142
 
137
143
  # @private
138
144
  def description
139
- "change #{@change_details.message} " \
145
+ "change #{@change_details.value_representation} " \
140
146
  "#{@relativity.to_s.tr('_', ' ')} #{description_of @expected_delta}"
141
147
  end
142
148
 
@@ -175,7 +181,7 @@ module RSpec
175
181
 
176
182
  # @private
177
183
  def description
178
- "change #{@change_details.message} #{change_description}"
184
+ "change #{@change_details.value_representation} #{change_description}"
179
185
  end
180
186
 
181
187
  # @private
@@ -202,30 +208,30 @@ module RSpec
202
208
  end
203
209
 
204
210
  def before_value_failure
205
- "expected #{@change_details.message} " \
211
+ "expected #{@change_details.value_representation} " \
206
212
  "to have initially been #{description_of @expected_before}, " \
207
213
  "but was #{description_of @change_details.actual_before}"
208
214
  end
209
215
 
210
216
  def after_value_failure
211
- "expected #{@change_details.message} " \
217
+ "expected #{@change_details.value_representation} " \
212
218
  "to have changed to #{description_of @expected_after}, " \
213
219
  "but is now #{description_of @change_details.actual_after}"
214
220
  end
215
221
 
216
222
  def did_not_change_failure
217
- "expected #{@change_details.message} " \
223
+ "expected #{@change_details.value_representation} " \
218
224
  "to have changed #{change_description}, but did not change"
219
225
  end
220
226
 
221
227
  def did_change_failure
222
- "expected #{@change_details.message} not to have changed, but " \
228
+ "expected #{@change_details.value_representation} not to have changed, but " \
223
229
  "did change from #{description_of @change_details.actual_before} " \
224
230
  "to #{description_of @change_details.actual_after}"
225
231
  end
226
232
 
227
233
  def not_given_a_block_failure
228
- "expected #{@change_details.message} to have changed " \
234
+ "expected #{@change_details.value_representation} to have changed " \
229
235
  "#{change_description}, but was not given a block"
230
236
  end
231
237
  end
@@ -307,9 +313,9 @@ module RSpec
307
313
  # @private
308
314
  # Encapsulates the details of the before/after values.
309
315
  class ChangeDetails
310
- attr_reader :message, :actual_before, :actual_after
316
+ attr_reader :actual_before, :actual_after
311
317
 
312
- def initialize(receiver=nil, message=nil, &block)
318
+ def initialize(matcher_name, receiver=nil, message=nil, &block)
313
319
  if receiver && !message
314
320
  raise(
315
321
  ArgumentError,
@@ -318,8 +324,22 @@ module RSpec
318
324
  "You passed an object but no message."
319
325
  )
320
326
  end
321
- @message = message ? "##{message}" : "result"
322
- @value_proc = block || lambda { receiver.__send__(message) }
327
+
328
+ @matcher_name = matcher_name
329
+ @receiver = receiver
330
+ @message = message
331
+ @value_proc = block
332
+ end
333
+
334
+ def value_representation
335
+ @value_representation ||=
336
+ if @message
337
+ "##{@message}"
338
+ elsif (value_block_snippet = extract_value_block_snippet)
339
+ "`#{value_block_snippet}`"
340
+ else
341
+ 'result'
342
+ end
323
343
  end
324
344
 
325
345
  def perform_change(event_proc)
@@ -339,7 +359,9 @@ module RSpec
339
359
  private
340
360
 
341
361
  def evaluate_value_proc
342
- case val = @value_proc.call
362
+ value_proc = @value_proc || lambda { @receiver.__send__(@message) }
363
+
364
+ case val = value_proc.call
343
365
  when IO # enumerable, but we don't want to dup it.
344
366
  val
345
367
  when Enumerable, String
@@ -348,6 +370,17 @@ module RSpec
348
370
  val
349
371
  end
350
372
  end
373
+
374
+ if RSpec::Support::RubyFeatures.ripper_supported?
375
+ def extract_value_block_snippet
376
+ return nil unless @value_proc
377
+ Expectations::BlockSnippetExtractor.try_extracting_single_line_body_of(@value_proc, @matcher_name)
378
+ end
379
+ else
380
+ def extract_value_block_snippet
381
+ nil
382
+ end
383
+ end
351
384
  end
352
385
  end
353
386
  end
@@ -172,7 +172,7 @@ module RSpec
172
172
  "including those raised by Ruby (e.g. NoMethodError, NameError " \
173
173
  "and ArgumentError), meaning the code you are intending to test " \
174
174
  "may not even get reached. Instead consider using " \
175
- "`expect {}.not_to raise_error` or `expect { }.to raise_error" \
175
+ "`expect { }.not_to raise_error` or `expect { }.to raise_error" \
176
176
  "(DifferentSpecificErrorClass)`. This message can be suppressed by " \
177
177
  "setting: `RSpec::Expectations.configuration.on_potential_false" \
178
178
  "_positives = :nothing`")
@@ -5,10 +5,7 @@ module RSpec
5
5
  # Provides the implementation for `satisfy`.
6
6
  # Not intended to be instantiated directly.
7
7
  class Satisfy < BaseMatcher
8
- # @private
9
- attr_reader :description
10
-
11
- def initialize(description="satisfy block", &block)
8
+ def initialize(description=nil, &block)
12
9
  @description = description
13
10
  @block = block
14
11
  end
@@ -20,6 +17,11 @@ module RSpec
20
17
  @block.call(actual)
21
18
  end
22
19
 
20
+ # @private
21
+ def description
22
+ @description ||= "satisfy #{block_representation}"
23
+ end
24
+
23
25
  # @api private
24
26
  # @return [String]
25
27
  def failure_message
@@ -31,6 +33,27 @@ module RSpec
31
33
  def failure_message_when_negated
32
34
  "expected #{actual_formatted} not to #{description}"
33
35
  end
36
+
37
+ private # rubocop:disable Lint/UselessAccessModifier
38
+
39
+ if RSpec::Support::RubyFeatures.ripper_supported?
40
+ def block_representation
41
+ if (block_snippet = extract_block_snippet)
42
+ "expression `#{block_snippet}`"
43
+ else
44
+ 'block'
45
+ end
46
+ end
47
+
48
+ def extract_block_snippet
49
+ return nil unless @block
50
+ Expectations::BlockSnippetExtractor.try_extracting_single_line_body_of(@block, matcher_name)
51
+ end
52
+ else
53
+ def block_representation
54
+ 'block'
55
+ end
56
+ end
34
57
  end
35
58
  end
36
59
  end
@@ -8,19 +8,17 @@ module RSpec
8
8
  # yield matchers is used. Provides information about
9
9
  # the yield behavior of the object-under-test.
10
10
  class YieldProbe
11
- def self.probe(block)
12
- probe = new(block)
11
+ def self.probe(block, &callback)
12
+ probe = new(block, &callback)
13
13
  return probe unless probe.has_block?
14
- probe.assert_valid_expect_block!
15
- block.call(probe)
16
- probe.assert_used!
17
- probe
14
+ probe.probe
18
15
  end
19
16
 
20
17
  attr_accessor :num_yields, :yielded_args
21
18
 
22
- def initialize(block)
19
+ def initialize(block, &callback)
23
20
  @block = block
21
+ @callback = callback || Proc.new {}
24
22
  @used = false
25
23
  self.num_yields = 0
26
24
  self.yielded_args = []
@@ -30,13 +28,22 @@ module RSpec
30
28
  Proc === @block
31
29
  end
32
30
 
31
+ def probe
32
+ assert_valid_expect_block!
33
+ @block.call(self)
34
+ assert_used!
35
+ self
36
+ end
37
+
33
38
  def to_proc
34
39
  @used = true
35
40
 
36
41
  probe = self
42
+ callback = @callback
37
43
  Proc.new do |*args|
38
44
  probe.num_yields += 1
39
45
  probe.yielded_args << args
46
+ callback.call(*args)
40
47
  nil # to indicate the block does not return a meaningful value
41
48
  end
42
49
  end
@@ -56,12 +63,6 @@ module RSpec
56
63
  end
57
64
  end
58
65
 
59
- def successive_yield_args
60
- yielded_args.map do |arg_array|
61
- arg_array.size == 1 ? arg_array.first : arg_array
62
- end
63
- end
64
-
65
66
  def assert_used!
66
67
  return if @used
67
68
  raise 'You must pass the argument yielded to your expect block on ' \
@@ -269,10 +270,15 @@ module RSpec
269
270
 
270
271
  # @private
271
272
  def matches?(block)
272
- @probe = YieldProbe.probe(block)
273
+ @args_matched_when_yielded = true
274
+ @probe = YieldProbe.new(block) do
275
+ @actual = @probe.single_yield_args
276
+ @actual_formatted = actual_formatted
277
+ @args_matched_when_yielded &&= args_currently_match?
278
+ end
273
279
  return false unless @probe.has_block?
274
- @actual = @probe.single_yield_args
275
- @probe.yielded_once?(:yield_with_args) && args_match?
280
+ @probe.probe
281
+ @probe.yielded_once?(:yield_with_args) && @args_matched_when_yielded
276
282
  end
277
283
 
278
284
  # @private
@@ -317,16 +323,16 @@ module RSpec
317
323
  def negative_failure_reason
318
324
  if !@probe.has_block?
319
325
  'was not a block'
320
- elsif all_args_match?
326
+ elsif @args_matched_when_yielded && !@expected.empty?
321
327
  'yielded with expected arguments' \
322
328
  "\nexpected not: #{surface_descriptions_in(@expected).inspect}" \
323
- "\n got: #{actual_formatted}"
329
+ "\n got: #{@actual_formatted}"
324
330
  else
325
331
  'did'
326
332
  end
327
333
  end
328
334
 
329
- def args_match?
335
+ def args_currently_match?
330
336
  if @expected.empty? # expect {...}.to yield_with_args
331
337
  @positive_args_failure = 'yielded with no arguments' if @actual.empty?
332
338
  return !@actual.empty?
@@ -335,7 +341,7 @@ module RSpec
335
341
  unless (match = all_args_match?)
336
342
  @positive_args_failure = 'yielded with unexpected arguments' \
337
343
  "\nexpected: #{surface_descriptions_in(@expected).inspect}" \
338
- "\n got: #{actual_formatted}"
344
+ "\n got: #{@actual_formatted}"
339
345
  end
340
346
 
341
347
  match
@@ -356,10 +362,21 @@ module RSpec
356
362
 
357
363
  # @private
358
364
  def matches?(block)
359
- @probe = YieldProbe.probe(block)
365
+ @actual_formatted = []
366
+ @actual = []
367
+ args_matched_when_yielded = true
368
+ yield_count = 0
369
+
370
+ @probe = YieldProbe.probe(block) do |*arg_array|
371
+ arg_or_args = arg_array.size == 1 ? arg_array.first : arg_array
372
+ @actual_formatted << RSpec::Support::ObjectFormatter.format(arg_or_args)
373
+ @actual << arg_or_args
374
+ args_matched_when_yielded &&= values_match?(@expected[yield_count], arg_or_args)
375
+ yield_count += 1
376
+ end
377
+
360
378
  return false unless @probe.has_block?
361
- @actual = @probe.successive_yield_args
362
- args_match?
379
+ args_matched_when_yielded && yield_count == @expected.length
363
380
  end
364
381
 
365
382
  def does_not_match?(block)
@@ -390,10 +407,6 @@ module RSpec
390
407
 
391
408
  private
392
409
 
393
- def args_match?
394
- values_match?(@expected, @actual)
395
- end
396
-
397
410
  def expected_arg_description
398
411
  @expected.map { |e| description_of e }.join(', ')
399
412
  end
@@ -403,7 +416,7 @@ module RSpec
403
416
 
404
417
  'yielded with unexpected arguments' \
405
418
  "\nexpected: #{surface_descriptions_in(@expected).inspect}" \
406
- "\n got: #{actual_formatted}"
419
+ "\n got: [#{@actual_formatted.join(", ")}]"
407
420
  end
408
421
 
409
422
  def negative_failure_reason
@@ -411,7 +424,7 @@ module RSpec
411
424
 
412
425
  'yielded with expected arguments' \
413
426
  "\nexpected not: #{surface_descriptions_in(@expected).inspect}" \
414
- "\n got: #{actual_formatted}"
427
+ "\n got: [#{@actual_formatted.join(", ")}]"
415
428
  end
416
429
  end
417
430
  end
@@ -2,7 +2,70 @@ module RSpec
2
2
  module Matchers
3
3
  # Defines the custom matcher DSL.
4
4
  module DSL
5
+ # Defines a matcher alias. The returned matcher's `description` will be overriden
6
+ # to reflect the phrasing of the new name, which will be used in failure messages
7
+ # when passed as an argument to another matcher in a composed matcher expression.
8
+ #
9
+ # @example
10
+ # RSpec::Matchers.alias_matcher :a_list_that_sums_to, :sum_to
11
+ # sum_to(3).description # => "sum to 3"
12
+ # a_list_that_sums_to(3).description # => "a list that sums to 3"
13
+ #
14
+ # @example
15
+ # RSpec::Matchers.alias_matcher :a_list_sorted_by, :be_sorted_by do |description|
16
+ # description.sub("be sorted by", "a list sorted by")
17
+ # end
18
+ #
19
+ # be_sorted_by(:age).description # => "be sorted by age"
20
+ # a_list_sorted_by(:age).description # => "a list sorted by age"
21
+ #
22
+ # @param new_name [Symbol] the new name for the matcher
23
+ # @param old_name [Symbol] the original name for the matcher
24
+ # @param options [Hash] options for the aliased matcher
25
+ # @option options [Class] :klass the ruby class to use as the decorator. (Not normally used).
26
+ # @yield [String] optional block that, when given, is used to define the overriden
27
+ # logic. The yielded arg is the original description or failure message. If no
28
+ # block is provided, a default override is used based on the old and new names.
29
+ # @see RSpec::Matchers
30
+ def alias_matcher(new_name, old_name, options={}, &description_override)
31
+ description_override ||= lambda do |old_desc|
32
+ old_desc.gsub(EnglishPhrasing.split_words(old_name), EnglishPhrasing.split_words(new_name))
33
+ end
34
+ klass = options.fetch(:klass) { AliasedMatcher }
35
+
36
+ define_method(new_name) do |*args, &block|
37
+ matcher = __send__(old_name, *args, &block)
38
+ matcher.matcher_name = new_name if matcher.respond_to?(:matcher_name=)
39
+ klass.new(matcher, description_override)
40
+ end
41
+ end
42
+
43
+ # Defines a negated matcher. The returned matcher's `description` and `failure_message`
44
+ # will be overriden to reflect the phrasing of the new name, and the match logic will
45
+ # be based on the original matcher but negated.
46
+ #
47
+ # @example
48
+ # RSpec::Matchers.define_negated_matcher :exclude, :include
49
+ # include(1, 2).description # => "include 1 and 2"
50
+ # exclude(1, 2).description # => "exclude 1 and 2"
51
+ #
52
+ # @param negated_name [Symbol] the name for the negated matcher
53
+ # @param base_name [Symbol] the name of the original matcher that will be negated
54
+ # @yield [String] optional block that, when given, is used to define the overriden
55
+ # logic. The yielded arg is the original description or failure message. If no
56
+ # block is provided, a default override is used based on the old and new names.
57
+ # @see RSpec::Matchers
58
+ def define_negated_matcher(negated_name, base_name, &description_override)
59
+ alias_matcher(negated_name, base_name, :klass => AliasedNegatedMatcher, &description_override)
60
+ end
61
+
5
62
  # Defines a custom matcher.
63
+ #
64
+ # @param name [Symbol] the name for the matcher
65
+ # @yield [Object] block that is used to define the matcher.
66
+ # The block is evaluated in the context of your custom matcher class.
67
+ # When args are passed to your matcher, they will be yielded here,
68
+ # usually representing the expected value(s).
6
69
  # @see RSpec::Matchers
7
70
  def define(name, &declarations)
8
71
  warn_about_block_args(name, declarations)
@@ -266,7 +329,7 @@ module RSpec
266
329
  #
267
330
  # This compiles the user block into an actual method, allowing
268
331
  # them to use normal method constructs like `return`
269
- # (e.g. for a early guard statement), while allowing us to define
332
+ # (e.g. for an early guard statement), while allowing us to define
270
333
  # an override that can provide the wrapped handling
271
334
  # (e.g. assigning `@actual`, rescueing errors, etc) and
272
335
  # can `super` to the user's definition.
@@ -462,5 +525,3 @@ module RSpec
462
525
  end
463
526
  end
464
527
  end
465
-
466
- RSpec::Matchers.extend RSpec::Matchers::DSL
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-expectations
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.6.0.beta2
4
+ version: 3.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steven Baker
@@ -45,22 +45,22 @@ cert_chain:
45
45
  ZsVDj6a7lH3cNqtWXZxrb2wO38qV5AkYj8SQK7Hj3/Yui9myUX3crr+PdetazSqQ
46
46
  F3MdtaDehhjC
47
47
  -----END CERTIFICATE-----
48
- date: 2016-12-12 00:00:00.000000000 Z
48
+ date: 2017-05-04 00:00:00.000000000 Z
49
49
  dependencies:
50
50
  - !ruby/object:Gem::Dependency
51
51
  name: rspec-support
52
52
  requirement: !ruby/object:Gem::Requirement
53
53
  requirements:
54
- - - '='
54
+ - - "~>"
55
55
  - !ruby/object:Gem::Version
56
- version: 3.6.0.beta2
56
+ version: 3.6.0
57
57
  type: :runtime
58
58
  prerelease: false
59
59
  version_requirements: !ruby/object:Gem::Requirement
60
60
  requirements:
61
- - - '='
61
+ - - "~>"
62
62
  - !ruby/object:Gem::Version
63
- version: 3.6.0.beta2
63
+ version: 3.6.0
64
64
  - !ruby/object:Gem::Dependency
65
65
  name: diff-lcs
66
66
  requirement: !ruby/object:Gem::Requirement
@@ -150,6 +150,7 @@ files:
150
150
  - LICENSE.md
151
151
  - README.md
152
152
  - lib/rspec/expectations.rb
153
+ - lib/rspec/expectations/block_snippet_extractor.rb
153
154
  - lib/rspec/expectations/configuration.rb
154
155
  - lib/rspec/expectations/expectation_target.rb
155
156
  - lib/rspec/expectations/fail_with.rb
@@ -212,14 +213,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
212
213
  version: 1.8.7
213
214
  required_rubygems_version: !ruby/object:Gem::Requirement
214
215
  requirements:
215
- - - ">"
216
+ - - ">="
216
217
  - !ruby/object:Gem::Version
217
- version: 1.3.1
218
+ version: '0'
218
219
  requirements: []
219
220
  rubyforge_project:
220
- rubygems_version: 2.2.2
221
+ rubygems_version: 2.4.5.2
221
222
  signing_key:
222
223
  specification_version: 4
223
- summary: rspec-expectations-3.6.0.beta2
224
+ summary: rspec-expectations-3.6.0
224
225
  test_files: []
225
226
  has_rdoc:
metadata.gz.sig CHANGED
Binary file