rspec-expectations 2.0.0.a1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/.document +5 -0
  2. data/.gitignore +5 -0
  3. data/License.txt +22 -0
  4. data/README.markdown +8 -0
  5. data/Rakefile +43 -0
  6. data/VERSION +1 -0
  7. data/VERSION.yml +5 -0
  8. data/lib/rspec/expectations.rb +36 -0
  9. data/lib/rspec/expectations/differs/default.rb +62 -0
  10. data/lib/rspec/expectations/differs/load-diff-lcs.rb +12 -0
  11. data/lib/rspec/expectations/errors.rb +12 -0
  12. data/lib/rspec/expectations/extensions.rb +1 -0
  13. data/lib/rspec/expectations/extensions/kernel.rb +52 -0
  14. data/lib/rspec/expectations/fail_with.rb +43 -0
  15. data/lib/rspec/expectations/handler.rb +50 -0
  16. data/lib/rspec/matchers.rb +195 -0
  17. data/lib/rspec/matchers/be.rb +210 -0
  18. data/lib/rspec/matchers/be_close.rb +32 -0
  19. data/lib/rspec/matchers/be_instance_of.rb +26 -0
  20. data/lib/rspec/matchers/be_kind_of.rb +26 -0
  21. data/lib/rspec/matchers/change.rb +151 -0
  22. data/lib/rspec/matchers/compatibility.rb +14 -0
  23. data/lib/rspec/matchers/dsl.rb +14 -0
  24. data/lib/rspec/matchers/eql.rb +42 -0
  25. data/lib/rspec/matchers/equal.rb +53 -0
  26. data/lib/rspec/matchers/errors.rb +5 -0
  27. data/lib/rspec/matchers/exist.rb +16 -0
  28. data/lib/rspec/matchers/extensions/instance_exec.rb +23 -0
  29. data/lib/rspec/matchers/generated_descriptions.rb +36 -0
  30. data/lib/rspec/matchers/has.rb +35 -0
  31. data/lib/rspec/matchers/have.rb +151 -0
  32. data/lib/rspec/matchers/include.rb +44 -0
  33. data/lib/rspec/matchers/match.rb +21 -0
  34. data/lib/rspec/matchers/match_array.rb +71 -0
  35. data/lib/rspec/matchers/matcher.rb +86 -0
  36. data/lib/rspec/matchers/method_missing.rb +9 -0
  37. data/lib/rspec/matchers/operator_matcher.rb +78 -0
  38. data/lib/rspec/matchers/pretty.rb +37 -0
  39. data/lib/rspec/matchers/raise_error.rb +129 -0
  40. data/lib/rspec/matchers/respond_to.rb +71 -0
  41. data/lib/rspec/matchers/satisfy.rb +47 -0
  42. data/lib/rspec/matchers/simple_matcher.rb +133 -0
  43. data/lib/rspec/matchers/throw_symbol.rb +104 -0
  44. data/lib/rspec/matchers/wrap_expectation.rb +55 -0
  45. data/rspec-expectations.gemspec +104 -0
  46. data/spec/rspec/expectations/differs/default_spec.rb +128 -0
  47. data/spec/rspec/expectations/extensions/kernel_spec.rb +45 -0
  48. data/spec/rspec/expectations/fail_with_spec.rb +88 -0
  49. data/spec/rspec/expectations/handler_spec.rb +206 -0
  50. data/spec/rspec/expectations/wrap_expectation_spec.rb +30 -0
  51. data/spec/spec.opts +6 -0
  52. data/spec/spec_helper.rb +31 -0
  53. data/spec/suite.rb +1 -0
  54. data/spec/support/macros.rb +29 -0
  55. metadata +135 -0
@@ -0,0 +1,21 @@
1
+ module Rspec
2
+ module Matchers
3
+ # :call-seq:
4
+ # should match(pattern)
5
+ # should_not match(pattern)
6
+ #
7
+ # Given a Regexp or String, passes if actual.match(pattern)
8
+ #
9
+ # == Examples
10
+ #
11
+ # email.should match(/^([^\s]+)((?:[-a-z0-9]+\.)+[a-z]{2,})$/i)
12
+ # email.should match("@example.com")
13
+ def match(expected)
14
+ Matcher.new :match, expected do |_expected_|
15
+ match do |actual|
16
+ actual.match(_expected_)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,71 @@
1
+ module Rspec
2
+ module Matchers
3
+
4
+ class MatchArray #:nodoc:
5
+ include Rspec::Matchers::Pretty
6
+
7
+ def initialize(expected)
8
+ @expected = expected
9
+ end
10
+
11
+ def matches?(actual)
12
+ @actual = actual
13
+ @extra_items = difference_between_arrays(@actual, @expected)
14
+ @missing_items = difference_between_arrays(@expected, @actual)
15
+ @extra_items.empty? & @missing_items.empty?
16
+ end
17
+
18
+ def failure_message_for_should
19
+ message = "expected collection contained: #{safe_sort(@expected).inspect}\n"
20
+ message += "actual collection contained: #{safe_sort(@actual).inspect}\n"
21
+ message += "the missing elements were: #{safe_sort(@missing_items).inspect}\n" unless @missing_items.empty?
22
+ message += "the extra elements were: #{safe_sort(@extra_items).inspect}\n" unless @extra_items.empty?
23
+ message
24
+ end
25
+
26
+ def failure_message_for_should_not
27
+ "Matcher does not support should_not"
28
+ end
29
+
30
+ def description
31
+ "contain exactly #{_pretty_print(@expected)}"
32
+ end
33
+
34
+ private
35
+
36
+ def safe_sort(array)
37
+ array.all?{|item| item.respond_to?(:<=>)} ? array.sort : array
38
+ end
39
+
40
+ def difference_between_arrays(array_1, array_2)
41
+ difference = array_1.dup
42
+ array_2.each do |element|
43
+ if index = difference.index(element)
44
+ difference.delete_at(index)
45
+ end
46
+ end
47
+ difference
48
+ end
49
+
50
+
51
+ end
52
+
53
+ # :call-seq:
54
+ # should =~ expected
55
+ #
56
+ # Passes if actual contains all of the expected regardless of order.
57
+ # This works for collections. Pass in multiple args and it will only
58
+ # pass if all args are found in collection.
59
+ #
60
+ # NOTE: there is no should_not version of array.should =~ other_array
61
+ #
62
+ # == Examples
63
+ #
64
+ # [1,2,3].should =~ [1,2,3] # => would pass
65
+ # [1,2,3].should =~ [2,3,1] # => would pass
66
+ # [1,2,3,4].should =~ [1,2,3] # => would fail
67
+ # [1,2,2,3].should =~ [1,2,3] # => would fail
68
+ # [1,2,3].should =~ [1,2,3,4] # => would fail
69
+ OperatorMatcher.register(Array, '=~', Rspec::Matchers::MatchArray)
70
+ end
71
+ end
@@ -0,0 +1,86 @@
1
+ module Rspec
2
+ module Matchers
3
+ class Matcher
4
+ include Rspec::Matchers::Pretty
5
+
6
+ attr_reader :expected, :actual
7
+
8
+ def initialize(name, *expected, &declarations)
9
+ @name = name
10
+ @expected = expected
11
+ @actual = nil
12
+ @diffable = false
13
+ @messages = {
14
+ :description => lambda {"#{name_to_sentence}#{expected_to_sentence}"},
15
+ :failure_message_for_should => lambda {|actual| "expected #{actual.inspect} to #{name_to_sentence}#{expected_to_sentence}"},
16
+ :failure_message_for_should_not => lambda {|actual| "expected #{actual.inspect} not to #{name_to_sentence}#{expected_to_sentence}"}
17
+ }
18
+ making_declared_methods_public do
19
+ instance_exec(*@expected, &declarations)
20
+ end
21
+ end
22
+
23
+ def matches?(actual)
24
+ @actual = actual
25
+ instance_exec(@actual, &@match_block)
26
+ end
27
+
28
+ def description(&block)
29
+ cache_or_call_cached(:description, &block)
30
+ end
31
+
32
+ def failure_message_for_should(&block)
33
+ cache_or_call_cached(:failure_message_for_should, @actual, &block)
34
+ end
35
+
36
+ def failure_message_for_should_not(&block)
37
+ cache_or_call_cached(:failure_message_for_should_not, @actual, &block)
38
+ end
39
+
40
+ def match(&block)
41
+ @match_block = block
42
+ end
43
+
44
+ def diffable?
45
+ @diffable
46
+ end
47
+
48
+ def diffable
49
+ @diffable = true
50
+ end
51
+
52
+ private
53
+
54
+ def making_declared_methods_public # :nodoc:
55
+ # Our home-grown instance_exec in ruby 1.8.6 results in any methods
56
+ # declared in the block eval'd by instance_exec in the block to which we
57
+ # are yielding here are scoped private. This is NOT the case for Ruby
58
+ # 1.8.7 or 1.9.
59
+ #
60
+ # Also, due some crazy scoping that I don't understand, these methods
61
+ # are actually available in the specs (something about the matcher being
62
+ # defined in the scope of Rspec::Matchers or within an example), so not
63
+ # doing the following will not cause specs to fail, but they *will*
64
+ # cause features to fail and that will make users unhappy. So don't.
65
+ orig_private_methods = private_methods
66
+ yield
67
+ st = (class << self; self; end)
68
+ (private_methods - orig_private_methods).each {|m| st.__send__ :public, m}
69
+ end
70
+
71
+ def cache_or_call_cached(key, actual=nil, &block)
72
+ block ? @messages[key] = block :
73
+ actual.nil? ? @messages[key].call : @messages[key].call(actual)
74
+ end
75
+
76
+ def name_to_sentence
77
+ split_words(@name)
78
+ end
79
+
80
+ def expected_to_sentence
81
+ to_sentence(@expected)
82
+ end
83
+
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,9 @@
1
+ module Rspec
2
+ module Matchers
3
+ def method_missing(sym, *args, &block) # :nodoc:
4
+ return Matchers::Be.new(sym, *args) if sym.to_s =~ /^be_/
5
+ return Matchers::Has.new(sym, *args) if sym.to_s =~ /^have_/
6
+ super
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,78 @@
1
+ module Rspec
2
+ module Matchers
3
+
4
+ class OperatorMatcher
5
+ class << self
6
+ def registry
7
+ @registry ||= {}
8
+ end
9
+
10
+ def register(klass, operator, matcher)
11
+ registry[klass] ||= {}
12
+ registry[klass][operator] = matcher
13
+ end
14
+
15
+ def get(klass, operator)
16
+ registry[klass] && registry[klass][operator]
17
+ end
18
+ end
19
+
20
+ def initialize(actual)
21
+ @actual = actual
22
+ end
23
+
24
+ def self.use_custom_matcher_or_delegate(operator)
25
+ define_method(operator) do |expected|
26
+ if matcher = OperatorMatcher.get(@actual.class, operator)
27
+ @actual.send(::Rspec::Matchers.last_should, matcher.new(expected))
28
+ else
29
+ eval_match(@actual, operator, expected)
30
+ end
31
+ end
32
+ end
33
+
34
+ ['==', '===', '=~', '>', '>=', '<', '<='].each do |operator|
35
+ use_custom_matcher_or_delegate operator
36
+ end
37
+
38
+ def fail_with_message(message)
39
+ Rspec::Expectations.fail_with(message, @expected, @actual)
40
+ end
41
+
42
+ def description
43
+ "#{@operator} #{@expected.inspect}"
44
+ end
45
+
46
+ private
47
+
48
+ def eval_match(actual, operator, expected)
49
+ ::Rspec::Matchers.last_matcher = self
50
+ @operator, @expected = operator, expected
51
+ __delegate_operator(actual, operator, expected)
52
+ end
53
+
54
+ end
55
+
56
+ class PositiveOperatorMatcher < OperatorMatcher #:nodoc:
57
+ def __delegate_operator(actual, operator, expected)
58
+ if actual.__send__(operator, expected)
59
+ true
60
+ elsif ['==','===', '=~'].include?(operator)
61
+ fail_with_message("expected: #{expected.inspect},\n got: #{actual.inspect} (using #{operator})")
62
+ else
63
+ fail_with_message("expected: #{operator} #{expected.inspect},\n got: #{operator.gsub(/./, ' ')} #{actual.inspect}")
64
+ end
65
+ end
66
+
67
+ end
68
+
69
+ class NegativeOperatorMatcher < OperatorMatcher #:nodoc:
70
+ def __delegate_operator(actual, operator, expected)
71
+ return false unless actual.__send__(operator, expected)
72
+ return fail_with_message("expected not: #{operator} #{expected.inspect},\n got: #{operator.gsub(/./, ' ')} #{actual.inspect}")
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,37 @@
1
+ module Rspec
2
+ module Matchers
3
+ module Pretty
4
+ def split_words(sym)
5
+ sym.to_s.gsub(/_/,' ')
6
+ end
7
+
8
+ def to_sentence(words)
9
+ words = words.map{|w| w.inspect}
10
+ case words.length
11
+ when 0
12
+ ""
13
+ when 1
14
+ " #{words[0]}"
15
+ when 2
16
+ " #{words[0]} and #{words[1]}"
17
+ else
18
+ " #{words[0...-1].join(', ')}, and #{words[-1]}"
19
+ end
20
+ end
21
+
22
+ def _pretty_print(array)
23
+ result = ""
24
+ array.each_with_index do |item, index|
25
+ if index < (array.length - 2)
26
+ result << "#{item.inspect}, "
27
+ elsif index < (array.length - 1)
28
+ result << "#{item.inspect} and "
29
+ else
30
+ result << "#{item.inspect}"
31
+ end
32
+ end
33
+ result
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,129 @@
1
+ module Rspec
2
+ module Matchers
3
+ class RaiseError #:nodoc:
4
+ def initialize(expected_error_or_message=Exception, expected_message=nil, &block)
5
+ @block = block
6
+ @actual_error = nil
7
+ case expected_error_or_message
8
+ when String, Regexp
9
+ @expected_error, @expected_message = Exception, expected_error_or_message
10
+ else
11
+ @expected_error, @expected_message = expected_error_or_message, expected_message
12
+ end
13
+ end
14
+
15
+ def matches?(given_proc)
16
+ @raised_expected_error = false
17
+ @with_expected_message = false
18
+ @eval_block = false
19
+ @eval_block_passed = false
20
+ begin
21
+ given_proc.call
22
+ rescue @expected_error => @actual_error
23
+ @raised_expected_error = true
24
+ @with_expected_message = verify_message
25
+ rescue Exception => @actual_error
26
+ # This clause should be empty, but rcov will not report it as covered
27
+ # unless something (anything) is executed within the clause
28
+ rcov_error_report = "http://eigenclass.org/hiki.rb?rcov-0.8.0"
29
+ end
30
+
31
+ unless negative_expectation?
32
+ eval_block if @raised_expected_error && @with_expected_message && @block
33
+ end
34
+ ensure
35
+ return (@raised_expected_error & @with_expected_message) ? (@eval_block ? @eval_block_passed : true) : false
36
+ end
37
+
38
+ def eval_block
39
+ @eval_block = true
40
+ begin
41
+ @block[@actual_error]
42
+ @eval_block_passed = true
43
+ rescue Exception => err
44
+ @actual_error = err
45
+ end
46
+ end
47
+
48
+ def verify_message
49
+ case @expected_message
50
+ when nil
51
+ true
52
+ when Regexp
53
+ @expected_message =~ @actual_error.message
54
+ else
55
+ @expected_message == @actual_error.message
56
+ end
57
+ end
58
+
59
+ def failure_message_for_should
60
+ @eval_block ? @actual_error.message : "expected #{expected_error}#{given_error}"
61
+ end
62
+
63
+ def failure_message_for_should_not
64
+ "expected no #{expected_error}#{given_error}"
65
+ end
66
+
67
+ def description
68
+ "raise #{expected_error}"
69
+ end
70
+
71
+ private
72
+ def expected_error
73
+ case @expected_message
74
+ when nil
75
+ @expected_error
76
+ when Regexp
77
+ "#{@expected_error} with message matching #{@expected_message.inspect}"
78
+ else
79
+ "#{@expected_error} with #{@expected_message.inspect}"
80
+ end
81
+ end
82
+
83
+ def given_error
84
+ @actual_error.nil? ? " but nothing was raised" : ", got #{@actual_error.inspect}"
85
+ end
86
+
87
+ def negative_expectation?
88
+ # YES - I'm a bad person... help me find a better way - ryand
89
+ caller.first(3).find { |s| s =~ /should_not/ }
90
+ end
91
+ end
92
+
93
+ # :call-seq:
94
+ # should raise_error()
95
+ # should raise_error(NamedError)
96
+ # should raise_error(NamedError, String)
97
+ # should raise_error(NamedError, Regexp)
98
+ # should raise_error() { |error| ... }
99
+ # should raise_error(NamedError) { |error| ... }
100
+ # should raise_error(NamedError, String) { |error| ... }
101
+ # should raise_error(NamedError, Regexp) { |error| ... }
102
+ # should_not raise_error()
103
+ # should_not raise_error(NamedError)
104
+ # should_not raise_error(NamedError, String)
105
+ # should_not raise_error(NamedError, Regexp)
106
+ #
107
+ # With no args, matches if any error is raised.
108
+ # With a named error, matches only if that specific error is raised.
109
+ # With a named error and messsage specified as a String, matches only if both match.
110
+ # With a named error and messsage specified as a Regexp, matches only if both match.
111
+ # Pass an optional block to perform extra verifications on the exception matched
112
+ #
113
+ # == Examples
114
+ #
115
+ # lambda { do_something_risky }.should raise_error
116
+ # lambda { do_something_risky }.should raise_error(PoorRiskDecisionError)
117
+ # lambda { do_something_risky }.should raise_error(PoorRiskDecisionError) { |error| error.data.should == 42 }
118
+ # lambda { do_something_risky }.should raise_error(PoorRiskDecisionError, "that was too risky")
119
+ # lambda { do_something_risky }.should raise_error(PoorRiskDecisionError, /oo ri/)
120
+ #
121
+ # lambda { do_something_risky }.should_not raise_error
122
+ # lambda { do_something_risky }.should_not raise_error(PoorRiskDecisionError)
123
+ # lambda { do_something_risky }.should_not raise_error(PoorRiskDecisionError, "that was too risky")
124
+ # lambda { do_something_risky }.should_not raise_error(PoorRiskDecisionError, /oo ri/)
125
+ def raise_error(error=Exception, message=nil, &block)
126
+ Matchers::RaiseError.new(error, message, &block)
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,71 @@
1
+ module Rspec
2
+ module Matchers
3
+
4
+ class RespondTo #:nodoc:
5
+ def initialize(*names)
6
+ @names = names
7
+ @expected_arity = nil
8
+ @names_not_responded_to = []
9
+ end
10
+
11
+ def matches?(actual)
12
+ @actual = actual
13
+ @names.each do |name|
14
+ @names_not_responded_to << name unless actual.respond_to?(name) && matches_arity?(actual, name)
15
+ end
16
+ return @names_not_responded_to.empty?
17
+ end
18
+
19
+ def failure_message_for_should
20
+ "expected #{@actual.inspect} to respond to #{@names_not_responded_to.collect {|name| name.inspect }.join(', ')}#{with_arity}"
21
+ end
22
+
23
+ def failure_message_for_should_not
24
+ "expected #{@actual.inspect} not to respond to #{@names.collect {|name| name.inspect }.join(', ')}"
25
+ end
26
+
27
+ def description
28
+ "respond to #{pp_names}#{with_arity}"
29
+ end
30
+
31
+ def with(n)
32
+ @expected_arity = n
33
+ self
34
+ end
35
+
36
+ def argument
37
+ self
38
+ end
39
+ alias :arguments :argument
40
+
41
+ private
42
+
43
+ def matches_arity?(actual, name)
44
+ @expected_arity.nil?? true : @expected_arity == actual.method(name).arity
45
+ end
46
+
47
+ def with_arity
48
+ @expected_arity.nil?? "" :
49
+ " with #{@expected_arity} argument#{@expected_arity == 1 ? '' : 's'}"
50
+ end
51
+
52
+ def pp_names
53
+ # Ruby 1.9 returns the same thing for array.to_s as array.inspect, so just use array.inspect here
54
+ @names.length == 1 ? "##{@names.first}" : @names.inspect
55
+ end
56
+ end
57
+
58
+ # :call-seq:
59
+ # should respond_to(*names)
60
+ # should_not respond_to(*names)
61
+ #
62
+ # Matches if the target object responds to all of the names
63
+ # provided. Names can be Strings or Symbols.
64
+ #
65
+ # == Examples
66
+ #
67
+ def respond_to(*names)
68
+ Matchers::RespondTo.new(*names)
69
+ end
70
+ end
71
+ end