jferris-rr 0.7.1.0.1239654108

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 (130) hide show
  1. data/CHANGES +196 -0
  2. data/README.rdoc +329 -0
  3. data/Rakefile +77 -0
  4. data/lib/rr.rb +84 -0
  5. data/lib/rr/adapters/rr_methods.rb +122 -0
  6. data/lib/rr/adapters/rspec.rb +58 -0
  7. data/lib/rr/adapters/test_unit.rb +29 -0
  8. data/lib/rr/double.rb +212 -0
  9. data/lib/rr/double_definitions/child_double_definition_creator.rb +27 -0
  10. data/lib/rr/double_definitions/double_definition.rb +346 -0
  11. data/lib/rr/double_definitions/double_definition_creator.rb +167 -0
  12. data/lib/rr/double_definitions/double_definition_creator_proxy.rb +37 -0
  13. data/lib/rr/double_definitions/strategies/implementation/implementation_strategy.rb +15 -0
  14. data/lib/rr/double_definitions/strategies/implementation/proxy.rb +62 -0
  15. data/lib/rr/double_definitions/strategies/implementation/reimplementation.rb +14 -0
  16. data/lib/rr/double_definitions/strategies/implementation/strongly_typed_reimplementation.rb +17 -0
  17. data/lib/rr/double_definitions/strategies/scope/instance.rb +15 -0
  18. data/lib/rr/double_definitions/strategies/scope/instance_of_class.rb +46 -0
  19. data/lib/rr/double_definitions/strategies/scope/scope_strategy.rb +15 -0
  20. data/lib/rr/double_definitions/strategies/strategy.rb +70 -0
  21. data/lib/rr/double_definitions/strategies/verification/dont_allow.rb +34 -0
  22. data/lib/rr/double_definitions/strategies/verification/mock.rb +44 -0
  23. data/lib/rr/double_definitions/strategies/verification/stub.rb +45 -0
  24. data/lib/rr/double_definitions/strategies/verification/verification_strategy.rb +15 -0
  25. data/lib/rr/double_injection.rb +143 -0
  26. data/lib/rr/double_matches.rb +51 -0
  27. data/lib/rr/errors/argument_equality_error.rb +6 -0
  28. data/lib/rr/errors/double_definition_error.rb +6 -0
  29. data/lib/rr/errors/double_not_found_error.rb +6 -0
  30. data/lib/rr/errors/double_order_error.rb +6 -0
  31. data/lib/rr/errors/rr_error.rb +20 -0
  32. data/lib/rr/errors/spy_verification_errors/double_injection_not_found_error.rb +8 -0
  33. data/lib/rr/errors/spy_verification_errors/invocation_count_error.rb +8 -0
  34. data/lib/rr/errors/spy_verification_errors/spy_verification_error.rb +8 -0
  35. data/lib/rr/errors/subject_does_not_implement_method_error.rb +6 -0
  36. data/lib/rr/errors/subject_has_different_arity_error.rb +6 -0
  37. data/lib/rr/errors/times_called_error.rb +6 -0
  38. data/lib/rr/expectations/any_argument_expectation.rb +21 -0
  39. data/lib/rr/expectations/argument_equality_expectation.rb +41 -0
  40. data/lib/rr/expectations/times_called_expectation.rb +57 -0
  41. data/lib/rr/hash_with_object_id_key.rb +41 -0
  42. data/lib/rr/recorded_calls.rb +103 -0
  43. data/lib/rr/space.rb +123 -0
  44. data/lib/rr/spy_verification.rb +48 -0
  45. data/lib/rr/spy_verification_proxy.rb +18 -0
  46. data/lib/rr/times_called_matchers/any_times_matcher.rb +18 -0
  47. data/lib/rr/times_called_matchers/at_least_matcher.rb +15 -0
  48. data/lib/rr/times_called_matchers/at_most_matcher.rb +23 -0
  49. data/lib/rr/times_called_matchers/integer_matcher.rb +19 -0
  50. data/lib/rr/times_called_matchers/non_terminal.rb +27 -0
  51. data/lib/rr/times_called_matchers/proc_matcher.rb +11 -0
  52. data/lib/rr/times_called_matchers/range_matcher.rb +21 -0
  53. data/lib/rr/times_called_matchers/terminal.rb +20 -0
  54. data/lib/rr/times_called_matchers/times_called_matcher.rb +44 -0
  55. data/lib/rr/wildcard_matchers/anything.rb +18 -0
  56. data/lib/rr/wildcard_matchers/boolean.rb +23 -0
  57. data/lib/rr/wildcard_matchers/duck_type.rb +32 -0
  58. data/lib/rr/wildcard_matchers/hash_including.rb +29 -0
  59. data/lib/rr/wildcard_matchers/is_a.rb +25 -0
  60. data/lib/rr/wildcard_matchers/numeric.rb +13 -0
  61. data/lib/rr/wildcard_matchers/range.rb +7 -0
  62. data/lib/rr/wildcard_matchers/regexp.rb +7 -0
  63. data/lib/rr/wildcard_matchers/satisfy.rb +26 -0
  64. data/spec/core_spec_suite.rb +19 -0
  65. data/spec/environment_fixture_setup.rb +6 -0
  66. data/spec/high_level_spec.rb +368 -0
  67. data/spec/rr/adapters/rr_methods_argument_matcher_spec.rb +67 -0
  68. data/spec/rr/adapters/rr_methods_creator_spec.rb +149 -0
  69. data/spec/rr/adapters/rr_methods_space_spec.rb +115 -0
  70. data/spec/rr/adapters/rr_methods_spec_helper.rb +11 -0
  71. data/spec/rr/adapters/rr_methods_times_matcher_spec.rb +17 -0
  72. data/spec/rr/double_definitions/child_double_definition_creator_spec.rb +112 -0
  73. data/spec/rr/double_definitions/double_definition_creator_proxy_spec.rb +155 -0
  74. data/spec/rr/double_definitions/double_definition_creator_spec.rb +502 -0
  75. data/spec/rr/double_definitions/double_definition_spec.rb +1159 -0
  76. data/spec/rr/double_injection/double_injection_bind_spec.rb +111 -0
  77. data/spec/rr/double_injection/double_injection_dispatching_spec.rb +244 -0
  78. data/spec/rr/double_injection/double_injection_has_original_method_spec.rb +55 -0
  79. data/spec/rr/double_injection/double_injection_reset_spec.rb +90 -0
  80. data/spec/rr/double_injection/double_injection_spec.rb +77 -0
  81. data/spec/rr/double_injection/double_injection_verify_spec.rb +29 -0
  82. data/spec/rr/double_spec.rb +352 -0
  83. data/spec/rr/errors/rr_error_spec.rb +67 -0
  84. data/spec/rr/expectations/any_argument_expectation_spec.rb +47 -0
  85. data/spec/rr/expectations/anything_argument_equality_expectation_spec.rb +14 -0
  86. data/spec/rr/expectations/argument_equality_expectation_spec.rb +135 -0
  87. data/spec/rr/expectations/boolean_argument_equality_expectation_spec.rb +34 -0
  88. data/spec/rr/expectations/hash_including_argument_equality_expectation_spec.rb +82 -0
  89. data/spec/rr/expectations/hash_including_spec.rb +17 -0
  90. data/spec/rr/expectations/satisfy_argument_equality_expectation_spec.rb +59 -0
  91. data/spec/rr/expectations/satisfy_spec.rb +14 -0
  92. data/spec/rr/expectations/times_called_expectation/times_called_expectation_any_times_spec.rb +46 -0
  93. data/spec/rr/expectations/times_called_expectation/times_called_expectation_at_least_spec.rb +69 -0
  94. data/spec/rr/expectations/times_called_expectation/times_called_expectation_at_most_spec.rb +71 -0
  95. data/spec/rr/expectations/times_called_expectation/times_called_expectation_helper.rb +23 -0
  96. data/spec/rr/expectations/times_called_expectation/times_called_expectation_integer_spec.rb +104 -0
  97. data/spec/rr/expectations/times_called_expectation/times_called_expectation_proc_spec.rb +81 -0
  98. data/spec/rr/expectations/times_called_expectation/times_called_expectation_range_spec.rb +83 -0
  99. data/spec/rr/expectations/times_called_expectation/times_called_expectation_spec.rb +38 -0
  100. data/spec/rr/rspec/invocation_matcher_spec.rb +279 -0
  101. data/spec/rr/rspec/rspec_adapter_spec.rb +66 -0
  102. data/spec/rr/rspec/rspec_backtrace_tweaking_spec.rb +31 -0
  103. data/spec/rr/rspec/rspec_backtrace_tweaking_spec_fixture.rb +11 -0
  104. data/spec/rr/rspec/rspec_usage_spec.rb +86 -0
  105. data/spec/rr/space/hash_with_object_id_key_spec.rb +88 -0
  106. data/spec/rr/space/space_spec.rb +542 -0
  107. data/spec/rr/test_unit/test_helper.rb +7 -0
  108. data/spec/rr/test_unit/test_unit_backtrace_test.rb +35 -0
  109. data/spec/rr/test_unit/test_unit_integration_test.rb +57 -0
  110. data/spec/rr/times_called_matchers/any_times_matcher_spec.rb +47 -0
  111. data/spec/rr/times_called_matchers/at_least_matcher_spec.rb +55 -0
  112. data/spec/rr/times_called_matchers/at_most_matcher_spec.rb +70 -0
  113. data/spec/rr/times_called_matchers/integer_matcher_spec.rb +70 -0
  114. data/spec/rr/times_called_matchers/proc_matcher_spec.rb +55 -0
  115. data/spec/rr/times_called_matchers/range_matcher_spec.rb +76 -0
  116. data/spec/rr/times_called_matchers/times_called_matcher_spec.rb +118 -0
  117. data/spec/rr/wildcard_matchers/anything_spec.rb +24 -0
  118. data/spec/rr/wildcard_matchers/boolean_spec.rb +36 -0
  119. data/spec/rr/wildcard_matchers/duck_type_spec.rb +52 -0
  120. data/spec/rr/wildcard_matchers/is_a_spec.rb +32 -0
  121. data/spec/rr/wildcard_matchers/numeric_spec.rb +32 -0
  122. data/spec/rr/wildcard_matchers/range_spec.rb +35 -0
  123. data/spec/rr/wildcard_matchers/regexp_spec.rb +43 -0
  124. data/spec/rr_spec.rb +28 -0
  125. data/spec/rspec_spec_suite.rb +16 -0
  126. data/spec/spec_helper.rb +107 -0
  127. data/spec/spec_suite.rb +27 -0
  128. data/spec/spy_verification_spec.rb +129 -0
  129. data/spec/test_unit_spec_suite.rb +21 -0
  130. metadata +187 -0
@@ -0,0 +1,143 @@
1
+ module RR
2
+ # RR::DoubleInjection is the binding of an subject and a method.
3
+ # A double_injection has 0 to many Double objects. Each Double
4
+ # has Argument Expectations and Times called Expectations.
5
+ class DoubleInjection
6
+ include Space::Reader
7
+ MethodArguments = Struct.new(:arguments, :block)
8
+ attr_reader :subject, :method_name, :doubles
9
+
10
+ def initialize(subject, method_name)
11
+ @subject = subject
12
+ @method_name = method_name.to_sym
13
+ if object_has_method?(method_name)
14
+ begin
15
+ meta.__send__(:alias_method, original_method_name, method_name)
16
+ rescue NameError => e
17
+ subject.send(method_name)
18
+ meta.__send__(:alias_method, original_method_name, method_name)
19
+ end
20
+ end
21
+ @doubles = []
22
+ end
23
+
24
+ # RR::DoubleInjection#register_double adds the passed in Double
25
+ # into this DoubleInjection's list of Double objects.
26
+ def register_double(double)
27
+ @doubles << double
28
+ end
29
+
30
+ # RR::DoubleInjection#bind injects a method that acts as a dispatcher
31
+ # that dispatches to the matching Double when the method
32
+ # is called.
33
+ def bind
34
+ define_implementation_placeholder
35
+ returns_method = <<-METHOD
36
+ def #{@method_name}(*args, &block)
37
+ arguments = MethodArguments.new(args, block)
38
+ __send__('#{placeholder_name}', arguments)
39
+ end
40
+ METHOD
41
+ meta.class_eval(returns_method, __FILE__, __LINE__ - 5)
42
+ self
43
+ end
44
+
45
+ # RR::DoubleInjection#verify verifies each Double
46
+ # TimesCalledExpectation are met.
47
+ def verify
48
+ @doubles.each do |double|
49
+ double.verify
50
+ end
51
+ end
52
+
53
+ # RR::DoubleInjection#reset removes the injected dispatcher method.
54
+ # It binds the original method implementation on the subject
55
+ # if one exists.
56
+ def reset
57
+ meta.__send__(:remove_method, placeholder_name)
58
+ if object_has_original_method?
59
+ meta.__send__(:alias_method, @method_name, original_method_name)
60
+ meta.__send__(:remove_method, original_method_name)
61
+ else
62
+ meta.__send__(:remove_method, @method_name)
63
+ end
64
+ end
65
+
66
+ def call_original_method(*args, &block)
67
+ @subject.__send__(original_method_name, *args, &block)
68
+ end
69
+
70
+ def object_has_original_method?
71
+ object_has_method?(original_method_name)
72
+ end
73
+
74
+ protected
75
+ def define_implementation_placeholder
76
+ me = self
77
+ meta.__send__(:define_method, placeholder_name) do |arguments|
78
+ me.__send__(:call_method, arguments.arguments, arguments.block)
79
+ end
80
+ end
81
+
82
+ def call_method(args, block)
83
+ space.record_call(subject, method_name, args, block)
84
+ if double = find_double_to_attempt(args)
85
+ double.call(self, *args, &block)
86
+ else
87
+ double_not_found_error(*args)
88
+ end
89
+ end
90
+
91
+ def find_double_to_attempt(args)
92
+ matches = DoubleMatches.new(@doubles).find_all_matches(args)
93
+
94
+ unless matches.exact_terminal_doubles_to_attempt.empty?
95
+ return matches.exact_terminal_doubles_to_attempt.first
96
+ end
97
+
98
+ unless matches.exact_non_terminal_doubles_to_attempt.empty?
99
+ return matches.exact_non_terminal_doubles_to_attempt.last
100
+ end
101
+
102
+ unless matches.wildcard_terminal_doubles_to_attempt.empty?
103
+ return matches.wildcard_terminal_doubles_to_attempt.first
104
+ end
105
+
106
+ unless matches.wildcard_non_terminal_doubles_to_attempt.empty?
107
+ return matches.wildcard_non_terminal_doubles_to_attempt.last
108
+ end
109
+
110
+ unless matches.matching_doubles.empty?
111
+ return matches.matching_doubles.first # This will raise a TimesCalledError
112
+ end
113
+
114
+ return nil
115
+ end
116
+
117
+ def double_not_found_error(*args)
118
+ message =
119
+ "On subject #{subject},\n" <<
120
+ "unexpected method invocation:\n" <<
121
+ " #{Double.formatted_name(@method_name, args)}\n" <<
122
+ "expected invocations:\n" <<
123
+ Double.list_message_part(@doubles)
124
+ raise Errors::DoubleNotFoundError, message
125
+ end
126
+
127
+ def placeholder_name
128
+ "__rr__#{@method_name}"
129
+ end
130
+
131
+ def original_method_name
132
+ "__rr__original_#{@method_name}"
133
+ end
134
+
135
+ def object_has_method?(method_name)
136
+ @subject.methods.include?(method_name.to_s) || @subject.respond_to?(method_name)
137
+ end
138
+
139
+ def meta
140
+ (class << @subject; self; end)
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,51 @@
1
+ module RR
2
+ class DoubleMatches
3
+ attr_reader :matching_doubles,
4
+ :exact_terminal_doubles_to_attempt,
5
+ :exact_non_terminal_doubles_to_attempt,
6
+ :wildcard_terminal_doubles_to_attempt,
7
+ :wildcard_non_terminal_doubles_to_attempt
8
+ def initialize(doubles) #:nodoc:
9
+ @doubles = doubles
10
+ @matching_doubles = []
11
+ @exact_terminal_doubles_to_attempt = []
12
+ @exact_non_terminal_doubles_to_attempt = []
13
+ @wildcard_terminal_doubles_to_attempt = []
14
+ @wildcard_non_terminal_doubles_to_attempt = []
15
+ end
16
+
17
+ def find_all_matches(args)
18
+ @doubles.each do |double|
19
+ if double.exact_match?(*args)
20
+ matching_doubles << double
21
+ if double.attempt?
22
+ exact_double_is_terminal_or_non_terminal double
23
+ end
24
+ elsif double.wildcard_match?(*args)
25
+ matching_doubles << double
26
+ if double.attempt?
27
+ wildcard_double_is_terminal_or_non_terminal double
28
+ end
29
+ end
30
+ end
31
+ self
32
+ end
33
+
34
+ protected
35
+ def exact_double_is_terminal_or_non_terminal(double)
36
+ if double.terminal?
37
+ exact_terminal_doubles_to_attempt << double
38
+ else
39
+ exact_non_terminal_doubles_to_attempt << double
40
+ end
41
+ end
42
+
43
+ def wildcard_double_is_terminal_or_non_terminal(double)
44
+ if double.terminal?
45
+ wildcard_terminal_doubles_to_attempt << double
46
+ else
47
+ wildcard_non_terminal_doubles_to_attempt << double
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,6 @@
1
+ module RR
2
+ module Errors
3
+ class ArgumentEqualityError < RRError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module RR
2
+ module Errors
3
+ class DoubleDefinitionError < RRError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module RR
2
+ module Errors
3
+ class DoubleNotFoundError < RRError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module RR
2
+ module Errors
3
+ class DoubleOrderError < RRError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,20 @@
1
+ module RR
2
+ module Errors
3
+ BACKTRACE_IDENTIFIER = Regexp.new("/lib/rr")
4
+
5
+ class RRError < RuntimeError
6
+ attr_writer :backtrace
7
+ def backtrace
8
+ original_backtrace = (@backtrace) ? @backtrace : super
9
+ return original_backtrace unless RR.trim_backtrace
10
+
11
+ return original_backtrace unless original_backtrace.respond_to?(:each)
12
+ new_backtrace = []
13
+ original_backtrace.each do |line|
14
+ new_backtrace << line unless line =~ BACKTRACE_IDENTIFIER
15
+ end
16
+ new_backtrace
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,8 @@
1
+ module RR
2
+ module Errors
3
+ module SpyVerificationErrors
4
+ class DoubleInjectionNotFoundError < SpyVerificationError
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module RR
2
+ module Errors
3
+ module SpyVerificationErrors
4
+ class InvocationCountError < SpyVerificationError
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module RR
2
+ module Errors
3
+ module SpyVerificationErrors
4
+ class SpyVerificationError < RRError
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,6 @@
1
+ module RR
2
+ module Errors
3
+ class SubjectDoesNotImplementMethodError < RRError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module RR
2
+ module Errors
3
+ class SubjectHasDifferentArityError < RRError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module RR
2
+ module Errors
3
+ class TimesCalledError < RRError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,21 @@
1
+ module RR
2
+ module Expectations
3
+ class AnyArgumentExpectation < ArgumentEqualityExpectation #:nodoc:
4
+ def initialize
5
+ super
6
+ end
7
+
8
+ def exact_match?(*arguments)
9
+ false
10
+ end
11
+
12
+ def wildcard_match?(*arguments)
13
+ true
14
+ end
15
+
16
+ def ==(other)
17
+ other.is_a?(self.class)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,41 @@
1
+ module RR
2
+ module Expectations
3
+ class ArgumentEqualityExpectation #:nodoc:
4
+ attr_reader :expected_arguments
5
+
6
+ def initialize(*expected_arguments)
7
+ @expected_arguments = expected_arguments
8
+ end
9
+
10
+ def exact_match?(*arguments)
11
+ return false unless arguments.length == expected_arguments.length
12
+ arguments.each_with_index do |arg, index|
13
+ return false unless equality_match(expected_arguments[index], arg)
14
+ end
15
+ true
16
+ end
17
+
18
+ def wildcard_match?(*arguments)
19
+ return false unless arguments.length == expected_arguments.length
20
+ arguments.each_with_index do |arg, index|
21
+ expected_argument = expected_arguments[index]
22
+ if expected_argument.respond_to?(:wildcard_match?)
23
+ return false unless expected_argument.wildcard_match?(arg)
24
+ else
25
+ return false unless equality_match(expected_argument, arg)
26
+ end
27
+ end
28
+ true
29
+ end
30
+
31
+ def ==(other)
32
+ expected_arguments == other.expected_arguments
33
+ end
34
+
35
+ protected
36
+ def equality_match(arg1, arg2)
37
+ arg1.respond_to?(:'__rr__original_==') ? arg1.__send__(:'__rr__original_==', arg2) : arg1 == arg2
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,57 @@
1
+ module RR
2
+ module Expectations
3
+ class TimesCalledExpectation #:nodoc:
4
+ attr_reader :double, :times_called
5
+
6
+ def initialize(double)
7
+ @double = double
8
+ @times_called = 0
9
+ @verify_backtrace = caller[1..-1]
10
+ end
11
+
12
+ def attempt?
13
+ times_matcher.attempt?(@times_called)
14
+ end
15
+
16
+ def attempt
17
+ @times_called += 1
18
+ verify_input_error unless times_matcher.possible_match?(@times_called)
19
+ return
20
+ end
21
+
22
+ def verify
23
+ return false unless times_matcher.is_a?(TimesCalledMatchers::TimesCalledMatcher)
24
+ return times_matcher.matches?(@times_called)
25
+ end
26
+
27
+ def verify!
28
+ unless verify
29
+ if @verify_backtrace
30
+ error = Errors::TimesCalledError.new(error_message)
31
+ error.backtrace = @verify_backtrace
32
+ raise error
33
+ else
34
+ raise Errors::TimesCalledError, error_message
35
+ end
36
+ end
37
+ end
38
+
39
+ def terminal?
40
+ times_matcher.terminal?
41
+ end
42
+
43
+ protected
44
+ def times_matcher
45
+ double.definition.times_matcher
46
+ end
47
+
48
+ def verify_input_error
49
+ raise Errors::TimesCalledError, error_message
50
+ end
51
+
52
+ def error_message
53
+ "#{double.formatted_name}\n#{times_matcher.error_message(@times_called)}"
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,41 @@
1
+ module RR
2
+ class HashWithObjectIdKey < ::Hash #:nodoc:
3
+ def initialize
4
+ @keys = {}
5
+ super do |hash, subject_object|
6
+ hash.set_with_object_id(subject_object, {})
7
+ end
8
+ end
9
+
10
+ alias_method :get_with_object_id, :[]
11
+ def [](key)
12
+ @keys[key.__id__] = key
13
+ super(key.__id__)
14
+ end
15
+
16
+ alias_method :set_with_object_id, :[]=
17
+ def []=(key, value)
18
+ @keys[key.__id__] = key
19
+ super(key.__id__, value)
20
+ end
21
+
22
+ def each
23
+ super do |object_id, value|
24
+ yield @keys[object_id], value
25
+ end
26
+ end
27
+
28
+ def delete(key)
29
+ @keys.delete(key.__id__)
30
+ super(key.__id__)
31
+ end
32
+
33
+ def keys
34
+ @keys.values
35
+ end
36
+
37
+ def include?(key)
38
+ super(key.__id__)
39
+ end
40
+ end
41
+ end