rr 1.2.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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