rr 1.2.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGES.md +25 -0
  3. data/Gemfile +1 -8
  4. data/README.md +8 -13
  5. data/Rakefile +16 -6
  6. data/lib/rr/class_instance_method_defined.rb +1 -1
  7. data/lib/rr/double.rb +28 -10
  8. data/lib/rr/double_definitions/double_definition.rb +39 -16
  9. data/lib/rr/double_definitions/double_definition_create.rb +5 -5
  10. data/lib/rr/double_definitions/double_definition_create_blank_slate.rb +10 -4
  11. data/lib/rr/double_definitions/strategies/strategy.rb +27 -8
  12. data/lib/rr/double_definitions/strategies/verification/mock.rb +8 -2
  13. data/lib/rr/double_matches.rb +4 -3
  14. data/lib/rr/expectations/any_argument_expectation.rb +4 -4
  15. data/lib/rr/expectations/argument_equality_expectation.rb +43 -5
  16. data/lib/rr/injections/double_injection.rb +65 -18
  17. data/lib/rr/injections/method_missing_injection.rb +36 -9
  18. data/lib/rr/keyword_arguments.rb +15 -0
  19. data/lib/rr/method_dispatches/base_method_dispatch.rb +22 -5
  20. data/lib/rr/method_dispatches/method_dispatch.rb +21 -10
  21. data/lib/rr/method_dispatches/method_missing_dispatch.rb +14 -5
  22. data/lib/rr/recorded_call.rb +9 -3
  23. data/lib/rr/recorded_calls.rb +17 -7
  24. data/lib/rr/space.rb +15 -5
  25. data/lib/rr/version.rb +1 -1
  26. data/lib/rr/without_autohook.rb +2 -1
  27. data/rr.gemspec +5 -0
  28. data/spec/suites.yml +1 -15
  29. data/spec/suites/rspec_2/unit/double_definitions/double_definition_create_spec.rb +18 -18
  30. data/spec/suites/rspec_2/unit/expectations/any_argument_expectation_spec.rb +9 -9
  31. data/spec/suites/rspec_2/unit/expectations/argument_equality_expectation_spec.rb +21 -21
  32. data/spec/suites/rspec_2/unit/expectations/boolean_argument_equality_expectation_spec.rb +4 -4
  33. data/spec/suites/rspec_2/unit/expectations/hash_including_argument_equality_expectation_spec.rb +31 -21
  34. data/spec/suites/rspec_2/unit/space_spec.rb +4 -3
  35. metadata +61 -10
  36. data/lib/rr/proc_from_block.rb +0 -11
  37. data/spec/suites/rspec_2/unit/proc_from_block_spec.rb +0 -14
  38. data/spec/suites/rspec_2_rails_4/integration/astc_rails_4_spec.rb +0 -142
  39. data/spec/suites/rspec_2_rails_4/integration/minitest_4_rails_4_spec.rb +0 -149
  40. data/spec/suites/rspec_2_rails_4/spec_helper.rb +0 -3
@@ -15,9 +15,10 @@ module RR
15
15
  @wildcard_non_terminal_doubles_to_attempt = []
16
16
  end
17
17
 
18
- def find_all_matches(args)
18
+ def find_all_matches(args, kwargs)
19
+ kwargs ||= {}
19
20
  @doubles.each do |double|
20
- if double.exact_match?(*args)
21
+ if double.exact_match?(args, kwargs)
21
22
  matching_doubles << double
22
23
  if double.attempt?
23
24
  if double.terminal?
@@ -26,7 +27,7 @@ module RR
26
27
  exact_non_terminal_doubles_to_attempt << double
27
28
  end
28
29
  end
29
- elsif double.wildcard_match?(*args)
30
+ elsif double.wildcard_match?(args, kwargs)
30
31
  matching_doubles << double
31
32
  if double.attempt?
32
33
  if double.terminal?
@@ -2,14 +2,14 @@ module RR
2
2
  module Expectations
3
3
  class AnyArgumentExpectation < ArgumentEqualityExpectation #:nodoc:
4
4
  def initialize
5
- super
5
+ super([], {})
6
6
  end
7
7
 
8
- def exact_match?(*arguments)
8
+ def exact_match?(arguments, keyword_arguments)
9
9
  false
10
10
  end
11
11
 
12
- def wildcard_match?(*arguments)
12
+ def wildcard_match?(arguments, keyword_arguments)
13
13
  true
14
14
  end
15
15
 
@@ -18,4 +18,4 @@ module RR
18
18
  end
19
19
  end
20
20
  end
21
- end
21
+ end
@@ -10,20 +10,37 @@ module RR
10
10
  end
11
11
 
12
12
  attr_reader :expected_arguments
13
+ attr_reader :expected_keyword_arguments
13
14
 
14
- def initialize(*expected_arguments)
15
+ def initialize(expected_arguments,
16
+ expected_keyword_arguments)
15
17
  @expected_arguments = expected_arguments
18
+ @expected_keyword_arguments = expected_keyword_arguments
16
19
  end
17
20
 
18
- def exact_match?(*arguments)
21
+ def exact_match?(arguments, keyword_arguments)
19
22
  return false unless arguments.length == expected_arguments.length
20
23
  arguments.each_with_index do |arg, index|
21
- return false unless self.class.recursive_safe_eq(expected_arguments[index], arg)
24
+ expected_arg = expected_arguments[index]
25
+ return false unless self.class.recursive_safe_eq(expected_arg, arg)
26
+ end
27
+ keywords = keyword_arguments.keys
28
+ expected_keywords = expected_keyword_arguments.keys
29
+ unless keywords.length == expected_keywords.length
30
+ return false
31
+ end
32
+ keywords.each do |keyword|
33
+ keyword_argument = keyword_arguments[keyword]
34
+ expected_keyword_argument = expected_keyword_arguments[keyword]
35
+ unless self.class.recursive_safe_eq(expected_keyword_argument,
36
+ keyword_argument)
37
+ return false
38
+ end
22
39
  end
23
40
  true
24
41
  end
25
42
 
26
- def wildcard_match?(*arguments)
43
+ def wildcard_match?(arguments, keyword_arguments)
27
44
  return false unless arguments.length == expected_arguments.length
28
45
  arguments.each_with_index do |arg, index|
29
46
  expected_argument = expected_arguments[index]
@@ -33,11 +50,32 @@ module RR
33
50
  return false unless self.class.recursive_safe_eq(expected_argument, arg)
34
51
  end
35
52
  end
53
+ keywords = keyword_arguments.keys
54
+ expected_keywords = expected_keyword_arguments.keys
55
+ unless keywords.length == expected_keywords.length
56
+ return false
57
+ end
58
+ keywords.each do |keyword|
59
+ keyword_argument = keyword_arguments[keyword]
60
+ expected_keyword_argument = expected_keyword_arguments[keyword]
61
+ if expected_keyword_argument.respond_to?(:wildcard_match?)
62
+ unless expected_keyword_argument.wildcard_match?(keyword_argument)
63
+ return false
64
+ end
65
+ else
66
+ unless self.class.recursive_safe_eq(expected_keyword_argument,
67
+ keyword_argument)
68
+ return false
69
+ end
70
+ end
71
+ end
36
72
  true
37
73
  end
38
74
 
39
75
  def ==(other)
40
- expected_arguments == other.expected_arguments
76
+ other.is_a?(self.class) and
77
+ expected_arguments == other.expected_arguments and
78
+ expected_keyword_arguments == other.expected_keyword_arguments
41
79
  end
42
80
  end
43
81
  end
@@ -31,15 +31,20 @@ module RR
31
31
  exists?((class << subject; self; end), method_name)
32
32
  end
33
33
 
34
- def dispatch_method(subject, subject_class, method_name, arguments, block)
34
+ def dispatch_method(subject,
35
+ subject_class,
36
+ method_name,
37
+ arguments,
38
+ keyword_arguments,
39
+ block)
35
40
  subject_eigenclass = (class << subject; self; end)
36
41
  if (
37
42
  exists?(subject_class, method_name) &&
38
- (subject_class == subject_eigenclass) || !subject.is_a?(Class)
43
+ ((subject_class == subject_eigenclass) || !subject.is_a?(Class))
39
44
  )
40
- find(subject_class, method_name.to_sym).dispatch_method(subject, arguments, block)
45
+ find(subject_class, method_name.to_sym).dispatch_method(subject, arguments, keyword_arguments, block)
41
46
  else
42
- new(subject_class, method_name.to_sym).dispatch_original_method(subject, arguments, block)
47
+ new(subject_class, method_name.to_sym).dispatch_original_method(subject, arguments, keyword_arguments, block)
43
48
  end
44
49
  end
45
50
 
@@ -91,7 +96,9 @@ module RR
91
96
 
92
97
  attr_reader :subject_class, :method_name, :doubles
93
98
 
94
- MethodArguments = Struct.new(:arguments, :block)
99
+ MethodArguments = Struct.new(:arguments,
100
+ :keyword_arguments,
101
+ :block)
95
102
 
96
103
  def initialize(subject_class, method_name)
97
104
  @subject_class = subject_class
@@ -111,7 +118,11 @@ module RR
111
118
  # is called.
112
119
  def bind
113
120
  if subject_has_method_defined?(method_name)
114
- bind_method_with_alias
121
+ if subject_has_original_method?
122
+ bind_method
123
+ else
124
+ bind_method_with_alias
125
+ end
115
126
  else
116
127
  Injections::MethodMissingInjection.find_or_create(subject_class)
117
128
  Injections::SingletonMethodAddedInjection.find_or_create(subject_class)
@@ -141,13 +152,37 @@ module RR
141
152
  id = BoundObjects.size
142
153
  BoundObjects[id] = subject_class
143
154
 
144
- subject_class.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
145
- def #{method_name}(*args, &block)
146
- arguments = MethodArguments.new(args, block)
147
- obj = ::RR::Injections::DoubleInjection::BoundObjects[#{id}]
148
- ::RR::Injections::DoubleInjection.dispatch_method(self, obj, :#{method_name}, arguments.arguments, arguments.block)
149
- end
150
- RUBY
155
+ if KeywordArguments.fully_supported?
156
+ subject_class.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
157
+ def #{method_name}(*args, **kwargs, &block)
158
+ arguments = MethodArguments.new(args, kwargs, block)
159
+ obj = ::RR::Injections::DoubleInjection::BoundObjects[#{id}]
160
+ ::RR::Injections::DoubleInjection.dispatch_method(
161
+ self,
162
+ obj,
163
+ :#{method_name},
164
+ arguments.arguments,
165
+ arguments.keyword_arguments,
166
+ arguments.block
167
+ )
168
+ end
169
+ RUBY
170
+ else
171
+ subject_class.class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
172
+ def #{method_name}(*args, &block)
173
+ arguments = MethodArguments.new(args, {}, block)
174
+ obj = ::RR::Injections::DoubleInjection::BoundObjects[#{id}]
175
+ ::RR::Injections::DoubleInjection.dispatch_method(
176
+ self,
177
+ obj,
178
+ :#{method_name},
179
+ arguments.arguments,
180
+ arguments.keyword_arguments,
181
+ arguments.block
182
+ )
183
+ end
184
+ RUBY
185
+ end
151
186
  self
152
187
  end
153
188
 
@@ -175,17 +210,29 @@ module RR
175
210
  end
176
211
  end
177
212
 
178
- def dispatch_method(subject, args, block)
213
+ def dispatch_method(subject, args, kwargs, block)
179
214
  if @dispatch_method_delegates_to_dispatch_original_method
180
- dispatch_original_method(subject, args, block)
215
+ dispatch_original_method(subject, args, kwargs, block)
181
216
  else
182
- dispatch = MethodDispatches::MethodDispatch.new(self, subject, args, block)
217
+ dispatch = MethodDispatches::MethodDispatch.new(
218
+ self,
219
+ subject,
220
+ args,
221
+ kwargs,
222
+ block
223
+ )
183
224
  dispatch.call
184
225
  end
185
226
  end
186
227
 
187
- def dispatch_original_method(subject, args, block)
188
- dispatch = MethodDispatches::MethodDispatch.new(self, subject, args, block)
228
+ def dispatch_original_method(subject, args, kwargs, block)
229
+ dispatch = MethodDispatches::MethodDispatch.new(
230
+ self,
231
+ subject,
232
+ args,
233
+ kwargs,
234
+ block
235
+ )
189
236
  dispatch.call_original_method
190
237
  end
191
238
 
@@ -71,16 +71,43 @@ module RR
71
71
  id = BoundObjects.size
72
72
  BoundObjects[id] = subject_class
73
73
 
74
- subject_class.class_eval((<<-METHOD), __FILE__, __LINE__ + 1)
75
- def method_missing(method_name, *args, &block)
76
- if respond_to_missing?(method_name, true)
77
- super
78
- else
79
- obj = ::RR::Injections::MethodMissingInjection::BoundObjects[#{id}]
80
- MethodDispatches::MethodMissingDispatch.new(self, obj, method_name, args, block).call
74
+ if KeywordArguments.fully_supported?
75
+ subject_class.class_eval((<<-METHOD), __FILE__, __LINE__ + 1)
76
+ def method_missing(method_name, *args, **kwargs, &block)
77
+ if respond_to_missing?(method_name, true)
78
+ super(method_name, *args, **kwargs, &block)
79
+ else
80
+ obj = ::RR::Injections::MethodMissingInjection::BoundObjects[#{id}]
81
+ MethodDispatches::MethodMissingDispatch.new(
82
+ self,
83
+ obj,
84
+ method_name,
85
+ args,
86
+ kwargs,
87
+ block
88
+ ).call
89
+ end
81
90
  end
82
- end
83
- METHOD
91
+ METHOD
92
+ else
93
+ subject_class.class_eval((<<-METHOD), __FILE__, __LINE__ + 1)
94
+ def method_missing(method_name, *args, &block)
95
+ if respond_to_missing?(method_name, true)
96
+ super(method_name, *args, &block)
97
+ else
98
+ obj = ::RR::Injections::MethodMissingInjection::BoundObjects[#{id}]
99
+ MethodDispatches::MethodMissingDispatch.new(
100
+ self,
101
+ obj,
102
+ method_name,
103
+ args,
104
+ {},
105
+ block
106
+ ).call
107
+ end
108
+ end
109
+ METHOD
110
+ end
84
111
  end
85
112
 
86
113
  def original_method_alias_name
@@ -0,0 +1,15 @@
1
+ module RR
2
+ module KeywordArguments
3
+ class << self
4
+ if (RUBY_VERSION <=> "3.0.0") >= 0
5
+ def fully_supported?
6
+ true
7
+ end
8
+ else
9
+ def fully_supported?
10
+ false
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -4,7 +4,7 @@ module RR
4
4
  extend Forwardable
5
5
  include Space::Reader
6
6
 
7
- attr_reader :args, :block, :double
7
+ attr_reader :args, :kwargs, :block, :double
8
8
 
9
9
  def call
10
10
  raise NotImplementedError
@@ -12,7 +12,7 @@ module RR
12
12
 
13
13
  protected
14
14
  def find_double_to_attempt
15
- matches = DoubleMatches.new(doubles).find_all_matches(args)
15
+ matches = DoubleMatches.new(doubles).find_all_matches(args, kwargs)
16
16
 
17
17
  unless matches.exact_terminal_doubles_to_attempt.empty?
18
18
  return matches.exact_terminal_doubles_to_attempt.first
@@ -47,8 +47,25 @@ module RR
47
47
  end
48
48
  end
49
49
 
50
- def call_original_method_missing
51
- subject.__send__(MethodMissingDispatch.original_method_missing_alias_name, method_name, *args, &block)
50
+ if KeywordArguments.fully_supported?
51
+ def call_original_method_missing
52
+ subject.__send__(
53
+ MethodMissingDispatch.original_method_missing_alias_name,
54
+ method_name,
55
+ *args,
56
+ **kwargs,
57
+ &block
58
+ )
59
+ end
60
+ else
61
+ def call_original_method_missing
62
+ subject.__send__(
63
+ MethodMissingDispatch.original_method_missing_alias_name,
64
+ method_name,
65
+ *args,
66
+ &block
67
+ )
68
+ end
52
69
  end
53
70
 
54
71
  def implementation_is_original_method?
@@ -70,7 +87,7 @@ module RR
70
87
  message =
71
88
  "On subject #{subject},\n" <<
72
89
  "unexpected method invocation:\n" <<
73
- " #{Double.formatted_name(method_name, args)}\n" <<
90
+ " #{Double.formatted_name(method_name, args, kwargs)}\n" <<
74
91
  "expected invocations:\n" <<
75
92
  Double.list_message_part(doubles)
76
93
  raise RR::Errors.build_error(:DoubleNotFoundError, message)
@@ -3,15 +3,19 @@ module RR
3
3
  class MethodDispatch < BaseMethodDispatch
4
4
  attr_reader :double_injection, :subject
5
5
 
6
- def initialize(double_injection, subject, args, block)
7
- @double_injection, @subject, @args, @block = double_injection, subject, args, block
6
+ def initialize(double_injection, subject, args, kwargs, block)
7
+ @double_injection = double_injection
8
+ @subject = subject
9
+ @args = args
10
+ @kwargs = kwargs
11
+ @block = block
8
12
  @double = find_double_to_attempt
9
13
  end
10
14
 
11
15
  def call
12
- space.record_call(subject, method_name, args, block)
16
+ space.record_call(subject, method_name, args, kwargs, block)
13
17
  if double
14
- double.method_call(args)
18
+ double.method_call(args, kwargs)
15
19
  call_yields
16
20
  return_value_1 = call_implementation
17
21
  return_value_2 = extract_subject_from_return_value(return_value_1)
@@ -27,11 +31,19 @@ module RR
27
31
 
28
32
  def call_original_method
29
33
  if subject_has_original_method?
30
- subject.__send__(original_method_alias_name, *args, &block)
34
+ if KeywordArguments.fully_supported?
35
+ subject.__send__(original_method_alias_name, *args, **kwargs, &block)
36
+ else
37
+ subject.__send__(original_method_alias_name, *args, &block)
38
+ end
31
39
  elsif subject_has_original_method_missing?
32
40
  call_original_method_missing
33
41
  else
34
- subject.__send__(:method_missing, method_name, *args, &block)
42
+ if KeywordArguments.fully_supported?
43
+ subject.__send__(:method_missing, method_name, *args, **kwargs, &block)
44
+ else
45
+ subject.__send__(:method_missing, method_name, *args, &block)
46
+ end
35
47
  end
36
48
  end
37
49
 
@@ -41,11 +53,10 @@ module RR
41
53
  call_original_method
42
54
  else
43
55
  if implementation
44
- if implementation.is_a?(Method)
45
- implementation.call(*args, &block)
56
+ if KeywordArguments.fully_supported?
57
+ implementation.call(*args, **kwargs, &block)
46
58
  else
47
- call_args = block ? args + [ProcFromBlock.new(&block)] : args
48
- implementation.call(*call_args)
59
+ implementation.call(*args, &block)
49
60
  end
50
61
  else
51
62
  nil
@@ -9,8 +9,13 @@ module RR
9
9
 
10
10
  attr_reader :subject, :subject_class, :method_name
11
11
 
12
- def initialize(subject, subject_class, method_name, args, block)
13
- @subject, @subject_class, @method_name, @args, @block = subject, subject_class, method_name, args, block
12
+ def initialize(subject, subject_class, method_name, args, kwargs, block)
13
+ @subject = subject
14
+ @subject_class = subject_class
15
+ @method_name = method_name
16
+ @args = args
17
+ @kwargs = kwargs
18
+ @block = block
14
19
  end
15
20
 
16
21
  def call
@@ -40,14 +45,18 @@ module RR
40
45
  protected
41
46
  def call_implementation
42
47
  if implementation_is_original_method?
43
- space.record_call(subject, method_name, args, block)
44
- double.method_call(args)
48
+ space.record_call(subject, method_name, args, kwargs, block)
49
+ double.method_call(args, kwargs)
45
50
  call_original_method
46
51
  else
47
52
  if double_injection = Injections::DoubleInjection.find(subject_class, method_name)
48
53
  double_injection.bind_method
49
54
  # The DoubleInjection takes care of calling double.method_call
50
- subject.__send__(method_name, *args, &block)
55
+ if KeywordArguments.fully_supported?
56
+ subject.__send__(method_name, *args, **kwargs, &block)
57
+ else
58
+ subject.__send__(method_name, *args, &block)
59
+ end
51
60
  else
52
61
  nil
53
62
  end