expectation 1.0.0 → 1.1.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: ba45fedcad85c3bc355804668620c834db45f2a1
4
- data.tar.gz: 19766d7a77e76bd18e51e246ab1fee0ed0be3209
3
+ metadata.gz: 0e82b68f20c078cadb99ae70a9fe2d3f73288680
4
+ data.tar.gz: 071efc275974c5a339a4f4178cdaf6dd042f398d
5
5
  SHA512:
6
- metadata.gz: 321eeaddc541552fd4261b5b6b9ca5f2095280abdc974ef3cb104077f5fee924d4d53cdf3218d839decf553099073276e0fa3d22dae07af26ca1ca77030e1654
7
- data.tar.gz: 6b40a3e8403b34dea1eac1d877b62c62a1a0d5d7de9798cda203e1492305e32fbe00ba5fc252803c746c6b9b4e47772a1c66d1787866f440e3a6f3aa88063551
6
+ metadata.gz: 7cb4aa141fe72c1ca03c748add680c855b8c20e2b946386c3380a342515054fc8de7b32e8f5c32cc3e46f7a18ee1c9a0c16f4e816bed7503a146ad65f455d59b
7
+ data.tar.gz: 6b9ebdad0ceadeb168cd26587a5ca0407b7e8c5151fa6daebced22366d0d1cd10ddee17033862ae2c612bf03c71ae4f82f6d33ea9d1c9f8474aafc8580fa1a01
data/README.md CHANGED
@@ -29,3 +29,4 @@ with a String entry at key `:foo`, and either an Array or nil at key `:bar`.
29
29
  ## License
30
30
 
31
31
  The expectations gem is distributed under the terms of the Modified BSD License, see LICENSE.BSD for details.
32
+
data/lib/contracts.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  #--
2
2
  # Author:: radiospiel (mailto:eno@radiospiel.org)
3
3
  # Copyright:: Copyright (c) 2011, 2012 radiospiel
4
- # License:: Distributes under the terms of the Modified BSD License, see LICENSE.BSD for details.
4
+ # License:: Distributes under the terms of the Modified BSD License,
5
+ # see LICENSE.BSD for details.
5
6
  #++
6
7
 
7
8
  # The Contract module provides annotation support. That basically
@@ -24,15 +25,23 @@
24
25
  # end
25
26
  # end
26
27
  #
28
+ require "logger"
29
+
27
30
  module Contracts
28
31
  class Error < ArgumentError; end
29
32
 
33
+ (class << self; self; end).class_eval do
34
+ attr :logger, true
35
+ end
36
+ self.logger = Logger.new(STDOUT)
37
+
30
38
  def self.current_contracts
31
39
  Thread.current[:current_contracts] ||= []
32
40
  end
33
41
 
34
42
  def self.consume_current_contracts
35
- r, Thread.current[:current_contracts] = Thread.current[:current_contracts], nil
43
+ r = Thread.current[:current_contracts]
44
+ Thread.current[:current_contracts] = nil
36
45
  r
37
46
  end
38
47
 
@@ -54,22 +63,45 @@ module Contracts
54
63
  end
55
64
 
56
65
  def invoke(receiver, *args, &blk)
66
+ #
67
+ # Some contracts might need a per-invocation scope. If that is the take
68
+ # their before_call method will return their specific scope, and we'll
69
+ # carry that over to the after_call and on_exception calls.
70
+ #
71
+ # Since this is potentially costly we do rather not create a combined
72
+ # scope object unless we really need it; also there is an optimized
73
+ # code path for after_call's in effect. (Not for on_exception though;
74
+ # since they should only occur in exceptional situations they can carry
75
+ # a bit of performance penalty just fine.)
76
+ #
77
+ # TODO: This could be improved by having a each annotation take care of
78
+ # each individual call; a first experiment to do that, however, failed.
79
+ annotation_scopes = nil
80
+
57
81
  @before_annotations.each do |annotation|
58
- annotation.before_call(receiver, *args, &blk)
82
+ next unless annotation_scope = annotation.before_call(receiver, *args, &blk)
83
+ annotation_scopes ||= {}
84
+ annotation_scopes[annotation.object_id] = annotation_scope
59
85
  end
60
86
 
61
87
  # instance methods are UnboundMethod, class methods are Method.
62
88
  rv = @method.is_a?(Method) ? @method.call(*args, &blk)
63
89
  : @method.bind(receiver).call(*args, &blk)
64
90
 
65
- @after_annotations.each do |annotation|
66
- annotation.after_call(rv, receiver, *args, &blk)
91
+ if annotation_scopes
92
+ @after_annotations.each do |annotation|
93
+ annotation.after_call(annotation_scopes[annotation.object_id], rv, receiver, *args, &blk)
94
+ end
95
+ else
96
+ @after_annotations.each do |annotation|
97
+ annotation.after_call(nil, rv, receiver, *args, &blk)
98
+ end
67
99
  end
68
100
 
69
101
  return rv
70
102
  rescue StandardError => exc
71
103
  @exception_annotations.each do |annotation|
72
- annotation.on_exception(exc, receiver, *args, &blk)
104
+ annotation.on_exception(annotation_scopes && annotation_scopes[annotation.object_id], exc, receiver, *args, &blk)
73
105
  end
74
106
  raise exc
75
107
  end
@@ -122,7 +154,7 @@ module Contracts
122
154
  #
123
155
  # Returns a description of the method; i.e. Class#name or Class.name
124
156
  def method_name
125
- if method.is_a?(Method) # A singleton method?
157
+ if method.is_a?(Method) # A singleton method?
126
158
  # The method owner is the singleton class of the class. Sadly, the
127
159
  # the singleton class has no name; hence we try to construct the name
128
160
  # from its to_s description.
@@ -132,66 +164,16 @@ module Contracts
132
164
  "#{method.owner}##{method.name}"
133
165
  end
134
166
  end
135
- end
136
- end
137
-
138
- require "expectation"
139
-
140
- class Contracts::Expects < Contracts::Base
141
- attr :expectations
142
-
143
- def initialize(expectations)
144
- @expectations = expectations
145
- end
146
-
147
- def before_call(receiver, *args, &blk)
148
- @parameter_names ||= method.parameters.map(&:last)
149
167
 
150
- @parameter_names.each_with_index do |parameter_name, idx|
151
- next unless expectation = expectations[parameter_name]
168
+ private
152
169
 
153
- Expectation.match! args[idx], expectation
170
+ def error!(message)
171
+ fail Contracts::Error, message, caller[6..-1]
154
172
  end
155
- rescue Expectation::Error
156
- raise Contracts::Error, "#{$!} in call to `#{method_name}`", caller[5..-1]
157
173
  end
158
174
  end
159
175
 
160
- module Contracts::ClassMethods
161
- def Expects(expectation)
162
- expect! expectation => Hash
163
- Contracts::Expects.new(expectation)
164
- end
165
- end
166
-
167
- class Contracts::Returns < Contracts::Base
168
- attr :expectation
169
-
170
- def initialize(expectation)
171
- @expectation = expectation
172
- end
173
-
174
- def after_call(rv, receiver, *args, &blk)
175
- Expectation.match! rv, expectation
176
- rescue Expectation::Error
177
- raise Contracts::Error, "#{$!} in return of `#{method_name}`", caller[5..-1]
178
- end
179
- end
180
-
181
- module Contracts::ClassMethods
182
- def Returns(expectation)
183
- Contracts::Returns.new(expectation)
184
- end
185
- end
186
-
187
- class Contracts::Nothrows < Contracts::Base
188
- def on_exception(rv, method, receiver, *args, &blk)
189
- raise Contracts::Error, "Nothrow method `#{method_name}` raised exception: #{$!}", caller[5..-1]
190
- end
191
- end
192
-
193
- module Contracts::ClassMethods
194
- def Nothrow
195
- Contracts::Nothrows.new
196
- end
197
- end
176
+ require_relative "contracts/expects.rb"
177
+ require_relative "contracts/nothrows.rb"
178
+ require_relative "contracts/returns.rb"
179
+ require_relative "contracts/runtime.rb"
@@ -0,0 +1,46 @@
1
+ #--
2
+ # Author:: radiospiel (mailto:eno@radiospiel.org)
3
+ # Copyright:: Copyright (c) 2011, 2012 radiospiel
4
+ # License:: Distributes under the terms of the Modified BSD License,
5
+ # see LICENSE.BSD for details.
6
+ #++
7
+
8
+ # The Contract module provides support for Expects annotations.
9
+
10
+ require "expectation"
11
+
12
+ class Contracts::Expects < Contracts::Base
13
+ attr_reader :expectations
14
+
15
+ def initialize(expectations)
16
+ @expectations = expectations
17
+ end
18
+
19
+ def before_call(_receiver, *args, &_blk)
20
+ args.each_with_index do |value, idx|
21
+ next unless expectation = expectations_ary[idx]
22
+ Expectation::Matcher.match! value, expectation
23
+ end
24
+
25
+ nil
26
+ rescue Expectation::Error
27
+ error! "#{$ERROR_INFO} in call to `#{method_name}`"
28
+ end
29
+
30
+ private
31
+
32
+ def expectations_ary
33
+ @expectations_ary ||= begin
34
+ method.parameters.map do |_flag, parameter_name|
35
+ expectations[parameter_name]
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ module Contracts::ClassMethods
42
+ def Expects(expectation)
43
+ expect! expectation => Hash
44
+ Contracts::Expects.new(expectation)
45
+ end
46
+ end
@@ -0,0 +1,18 @@
1
+ #--
2
+ # Author:: radiospiel (mailto:eno@radiospiel.org)
3
+ # Copyright:: Copyright (c) 2011, 2012 radiospiel
4
+ # License:: Distributes under the terms of the Modified BSD License,
5
+ # see LICENSE.BSD for details.
6
+ #++
7
+
8
+ class Contracts::Nothrows < Contracts::Base
9
+ def on_exception(_, _rv, _method, _receiver, *_args, &_blk)
10
+ error! "Nothrow method `#{method_name}` raised exception: #{$ERROR_INFO}"
11
+ end
12
+ end
13
+
14
+ module Contracts::ClassMethods
15
+ def Nothrow
16
+ Contracts::Nothrows.new
17
+ end
18
+ end
@@ -0,0 +1,26 @@
1
+ #--
2
+ # Author:: radiospiel (mailto:eno@radiospiel.org)
3
+ # Copyright:: Copyright (c) 2011, 2012 radiospiel
4
+ # License:: Distributes under the terms of the Modified BSD License,
5
+ # see LICENSE.BSD for details.
6
+ #++
7
+
8
+ class Contracts::Returns < Contracts::Base
9
+ attr_reader :expectation
10
+
11
+ def initialize(expectation)
12
+ @expectation = expectation
13
+ end
14
+
15
+ def after_call(_, rv, _receiver, *_args, &_blk)
16
+ Expectation::Matcher.match! rv, expectation
17
+ rescue Expectation::Error
18
+ error! "#{$ERROR_INFO} in return of `#{method_name}`"
19
+ end
20
+ end
21
+
22
+ module Contracts::ClassMethods
23
+ def Returns(expectation)
24
+ Contracts::Returns.new(expectation)
25
+ end
26
+ end
@@ -0,0 +1,47 @@
1
+ #--
2
+ # Author:: radiospiel (mailto:eno@radiospiel.org)
3
+ # Copyright:: Copyright (c) 2011, 2012 radiospiel
4
+ # License:: Distributes under the terms of the Modified BSD License,
5
+ # see LICENSE.BSD for details.
6
+ #++
7
+
8
+ class Contracts::Runtime < Contracts::Base
9
+ attr_reader :expected_runtime, :max
10
+
11
+ def initialize(expected_runtime, options)
12
+ @expected_runtime = expected_runtime
13
+ @max = options[:max]
14
+
15
+ expect! max.nil? || expected_runtime <= max
16
+ end
17
+
18
+ def before_call(_receiver, *_args, &_blk)
19
+ Time.now
20
+ end
21
+
22
+ def after_call(starts_at, _rv, _receiver, *_args, &_blk)
23
+ runtime = Time.now - starts_at
24
+
25
+ if max && runtime >= max
26
+ error! "#{method_name} took longer than allowed: %.02f secs > %.02f secs." % [runtime, expected_runtime]
27
+ end
28
+
29
+ if runtime >= expected_runtime
30
+ Contracts.logger.warn "#{method_name} took longer than expected: %.02f secs > %.02f secs." % [runtime, expected_runtime]
31
+ end
32
+ end
33
+
34
+ def logger
35
+ self.class.logger
36
+ end
37
+ end
38
+
39
+ module Contracts::ClassMethods
40
+ include Contracts
41
+
42
+ +Expects(expected_runtime: Numeric)
43
+ +Expects(options: { max: [Numeric, nil] })
44
+ def Runtime(expected_runtime, options = {})
45
+ Contracts::Runtime.new expected_runtime, options
46
+ end
47
+ end
@@ -1,15 +1,15 @@
1
1
  #--
2
2
  # Author:: radiospiel (mailto:eno@radiospiel.org)
3
3
  # Copyright:: Copyright (c) 2011, 2012 radiospiel
4
- # License:: Distributes under the terms of the Modified BSD License, see LICENSE.BSD for details.
4
+ # License:: Distributes under the terms of the Modified BSD License,
5
+ # see LICENSE.BSD for details.
5
6
  #++
6
7
 
7
8
  class Exception
8
- # Create an ArgumentError with an adjusted backtrace. We don't want to
9
+ # Create an ArgumentError with an adjusted backtrace. We don't want to
9
10
  # see the user all the annotation internals.
10
11
  def reraise_with_current_backtrace!
11
12
  set_backtrace caller[2..-1]
12
- raise self
13
+ fail self
13
14
  end
14
15
  end
15
-
data/lib/expectation.rb CHANGED
@@ -1,12 +1,15 @@
1
1
  #--
2
2
  # Author:: radiospiel (mailto:eno@radiospiel.org)
3
3
  # Copyright:: Copyright (c) 2011, 2012 radiospiel
4
- # License:: Distributes under the terms of the Modified BSD License, see LICENSE.BSD for details.
4
+ # License:: Distributes under the terms of the Modified BSD License,
5
+ # see LICENSE.BSD for details.
5
6
  #++
6
7
 
7
8
  module Expectation; end
8
9
 
9
10
  require_relative "core/exception"
11
+ require_relative "expectation/matcher"
12
+ require_relative "expectation/multi_matcher"
10
13
  require_relative "expectation/assertions"
11
14
 
12
15
  # The Expectation module implements methods to verify one or more values
@@ -16,13 +19,13 @@ require_relative "expectation/assertions"
16
19
  #
17
20
  # == Example
18
21
  #
19
- # This function expects a String argument starting with <tt>"http:"</tt>,
20
- # an Integer or Float argument, and a Hash with a String entry at key
22
+ # This function expects a String argument starting with <tt>"http:"</tt>,
23
+ # an Integer or Float argument, and a Hash with a String entry at key
21
24
  # <tt>:foo</tt>, and either an Array or +nil+ at key <tt>:bar</tt>.
22
- #
25
+ #
23
26
  # def function(a, b, options = {})
24
- # expect! a => /^http:/,
25
- # b => [Integer, Float],
27
+ # expect! a => /^http:/,
28
+ # b => [Integer, Float],
26
29
  # options => {
27
30
  # :foo => String,
28
31
  # :bar => [ Array, nil ]
@@ -30,77 +33,31 @@ require_relative "expectation/assertions"
30
33
  # end
31
34
 
32
35
  module Expectation
33
- class Error < ArgumentError
34
- attr :value, :expectation, :info
35
-
36
- def initialize(value, expectation, info = nil)
37
- @value, @expectation, @info =
38
- value, expectation, info
39
- end
40
-
41
- def to_s
42
- message = "#{value.inspect} does not match #{expectation.inspect}"
43
- message += ", #{info}" if info
44
- message
45
- end
46
- end
36
+ Error = Expectation::Matcher::Mismatch
47
37
 
48
38
  #
49
- # Verifies a number of expectations. If one or more expectations are
50
- # not met it raises an ArgumentError (on the first failing expectation).
51
- def expect!(*expectations, &block)
39
+ # Verifies a number of expectations. If one or more expectations are
40
+ # not met it raises an Error (on the first failing expectation).
41
+ #
42
+ # In contrast to the global expect! function this method does not
43
+ # adjust an Error's backtrace.
44
+ def self.expect!(*expectations, &block)
52
45
  expectations.each do |expectation|
53
46
  if expectation.is_a?(Hash)
54
47
  expectation.all? do |actual, exp|
55
- match! actual, exp
48
+ Matcher.match! actual, exp
56
49
  end
57
50
  else
58
- match! expectation, :truish
51
+ Matcher.match! expectation, :truish
59
52
  end
60
53
  end
61
54
 
62
- match! block, :__block if block
63
- rescue Error
64
- $!.reraise_with_current_backtrace!
65
- end
66
-
67
- #
68
- # Does a value match an expectation?
69
- def match?(value, expectation)
70
- match! value, expectation
71
- true
72
- rescue Error
73
- false
74
- end
75
-
76
- # Matches a value against an expectation. Raises an Expectation::Error
77
- # if the expectation could not be matched.
78
- def match!(value, expectation, key=nil)
79
- match = case expectation
80
- when :truish then !!value
81
- when :fail then false
82
- when Array then expectation.any? { |e| _match?(value, e) }
83
- when Proc then expectation.arity == 0 ? expectation.call : expectation.call(value)
84
- when Regexp then value.is_a?(String) && expectation =~ value
85
- when :__block then value.call
86
- when Hash then Hash === value &&
87
- expectation.each { |key, exp| match! value[key], exp, key }
88
- else expectation === value
89
- end
90
-
91
- return if match
92
-
93
- raise Error.new(value, expectation, key && "at key #{key.inspect}")
94
- end
95
-
96
- private
97
-
98
- def _match?(value, expectation)
99
- match! value, expectation
100
- true
101
- rescue Error
102
- false
55
+ Matcher.match! block, :__block if block
103
56
  end
104
57
  end
105
58
 
106
- Object.send :include, Expectation
59
+ def expect!(*args, &block)
60
+ Expectation.expect! *args, &block
61
+ rescue Expectation::Error
62
+ $ERROR_INFO.reraise_with_current_backtrace!
63
+ end
@@ -1,15 +1,15 @@
1
1
  #--
2
2
  # Author:: radiospiel (mailto:eno@radiospiel.org)
3
3
  # Copyright:: Copyright (c) 2011, 2012 radiospiel
4
- # License:: Distributes under the terms of the Modified BSD License, see LICENSE.BSD for details.
4
+ # License:: Distributes under the terms of the Modified BSD License,
5
+ # see LICENSE.BSD for details.
5
6
  #++
6
7
 
7
-
8
8
  # The Expectation::Assertions module provides expect! and inexpect!
9
9
  # assertions to use from within test cases.
10
10
  #
11
11
  # == Example
12
- #
12
+ #
13
13
  # class ExpectationTest < Test::Unit::TestCase
14
14
  # include Expectation::Assertions
15
15
  #
@@ -25,12 +25,12 @@ module Expectation::Assertions
25
25
  begin
26
26
  Expectation.expect!(*expectation, &block)
27
27
  rescue Expectation::Error
28
- exc = $!
28
+ exc = $ERROR_INFO
29
29
  end
30
30
 
31
31
  assert_block(exc && exc.message) { !exc }
32
32
  end
33
-
33
+
34
34
  # verifies the failure of the passed in expectations
35
35
  def inexpect!(*expectation, &block)
36
36
  exc = nil
@@ -38,7 +38,7 @@ module Expectation::Assertions
38
38
  begin
39
39
  Expectation.expect!(*expectation, &block)
40
40
  rescue Expectation::Error
41
- exc = $!
41
+ exc = $ERROR_INFO
42
42
  end
43
43
 
44
44
  assert_block("Expectation(s) should fail, but didn't") { exc }
@@ -0,0 +1,82 @@
1
+ #--
2
+ # Author:: radiospiel (mailto:eno@radiospiel.org)
3
+ # Copyright:: Copyright (c) 2011, 2012 radiospiel
4
+ # License:: Distributes under the terms of the Modified BSD License,
5
+ # see LICENSE.BSD for details.
6
+ #++
7
+
8
+ # The Expectation::Matcher module implements the logic to match a value
9
+ # against a pattern.
10
+
11
+ module Expectation::Matcher
12
+ class Mismatch < ArgumentError
13
+ attr_reader :value, :expectation, :info
14
+
15
+ def initialize(value, expectation, info = nil)
16
+ @value = value
17
+ @expectation = expectation
18
+ @info = info
19
+ end
20
+
21
+ def to_s
22
+ message = "#{value.inspect} does not match #{expectation.inspect}"
23
+ case info
24
+ when nil then message
25
+ when Fixnum then "#{message}, at index #{info}"
26
+ else "#{message}, at key #{info.inspect}"
27
+ end
28
+ end
29
+ end
30
+
31
+ extend self
32
+
33
+ #
34
+ # Does a value match an expectation?
35
+ def match?(value, expectation)
36
+ match! value, expectation
37
+ true
38
+ rescue Mismatch
39
+ false
40
+ end
41
+
42
+ #
43
+ # Matches a value against an expectation. Raises an Expectation::Mismatch
44
+ # if the expectation could not be matched.
45
+ #
46
+ # The info parameter is used to add some position information to
47
+ # any Mismatch raised.
48
+ def match!(value, expectation, info = nil)
49
+ match = case expectation
50
+ when :truish then !!value
51
+ when :fail then false
52
+ when Array then
53
+ if expectation.length == 1
54
+ # Array as "array of elements matching an expectation"; for example
55
+ # [1,2,3] => [Fixnum]
56
+ e = expectation.first
57
+ value.each_with_index { |v, idx| match!(v, e, idx) }
58
+ else
59
+ # Array as "object matching one of given expectations
60
+ expectation.any? { |e| _match?(value, e) }
61
+ end
62
+ when Proc then expectation.arity == 0 ? expectation.call : expectation.call(value)
63
+ when Regexp then value.is_a?(String) && expectation =~ value
64
+ when :__block then value.call
65
+ when Hash then Hash === value &&
66
+ expectation.each { |key, exp| match! value[key], exp, key }
67
+ else expectation === value
68
+ end
69
+
70
+ return if match
71
+ fail Mismatch.new(value, expectation, info)
72
+ end
73
+
74
+ private
75
+
76
+ def _match?(value, expectation)
77
+ match! value, expectation
78
+ true
79
+ rescue Mismatch
80
+ false
81
+ end
82
+ end
@@ -0,0 +1,25 @@
1
+ #--
2
+ # Author:: radiospiel (mailto:eno@radiospiel.org)
3
+ # Copyright:: Copyright (c) 2011, 2012 radiospiel
4
+ # License:: Distributes under the terms of the Modified BSD License,
5
+ # see LICENSE.BSD for details.
6
+ #++
7
+
8
+ # The Expectation::MultiMatcher class provides support for T1 | T2 matches.
9
+
10
+ class Expectation::MultiMatcher < Array
11
+ def initialize(lhs, rhs)
12
+ push lhs
13
+ push rhs
14
+ end
15
+
16
+ def |(other)
17
+ push other
18
+ end
19
+ end
20
+
21
+ class Module
22
+ def |(other)
23
+ Expectation::MultiMatcher.new(self, other)
24
+ end
25
+ end
@@ -1,8 +1,9 @@
1
1
  #--
2
2
  # Author:: radiospiel (mailto:eno@radiospiel.org)
3
3
  # Copyright:: Copyright (c) 2011, 2012 radiospiel
4
- # License:: Distributes under the terms of the Modified BSD License, see LICENSE.BSD for details.
4
+ # License:: Distributes under the terms of the Modified BSD License,
5
+ # see LICENSE.BSD for details.
5
6
  module Expectation
6
7
  # The expectation gem's version.
7
- VERSION="1.0.0"
8
+ VERSION = '1.1.0'
8
9
  end
@@ -4,30 +4,14 @@
4
4
  require_relative 'test_helper'
5
5
 
6
6
  require "contracts"
7
+ Contracts.logger.level = Logger::ERROR
7
8
 
8
9
  class ContractsTest < Test::Unit::TestCase
9
- class Foo
10
- include Contracts
11
-
12
- +Expects(a: 1)
13
- def sum(a, b, c)
14
- a + b + c
15
- end
16
-
17
- +Returns(2)
18
- def returns_arg(r)
19
- r
20
- end
21
-
22
- +Expects(v: Fixnum)
23
- def throw_on_one(v)
24
- raise if v == 1
25
- end
10
+ class Base
11
+ end
26
12
 
27
- +Nothrow()
28
- def unexpected_throw_on_one(v)
29
- raise if v == 1
30
- end
13
+ class Foo < Base
14
+ include Contracts
31
15
  end
32
16
 
33
17
  attr :foo
@@ -36,6 +20,15 @@ class ContractsTest < Test::Unit::TestCase
36
20
  @foo = Foo.new
37
21
  end
38
22
 
23
+ # -- Expects contracts ------------------------------------------------------
24
+
25
+ class Foo
26
+ +Expects(a: 1)
27
+ def sum(a, b, c)
28
+ a + b + c
29
+ end
30
+ end
31
+
39
32
  def test_contracts_check_number_of_arguments
40
33
  e = assert_raise(ArgumentError) {
41
34
  foo.sum(1) # scripts/test:67:in `f': wrong number of arguments (1 for 3) (ArgumentError)
@@ -54,6 +47,30 @@ class ContractsTest < Test::Unit::TestCase
54
47
  assert e.backtrace.first.include?("test/contracts_test.rb:")
55
48
  end
56
49
 
50
+ class Foo
51
+ attr :a, :b
52
+ +Expects(b: String)
53
+ def with_default_arg(a, b="check")
54
+ @a, @b = a, b
55
+ end
56
+ end
57
+
58
+ def test_default_args
59
+ foo.with_default_arg :one
60
+
61
+ assert_equal(foo.a, :one)
62
+ assert_equal(foo.b, "check")
63
+ end
64
+
65
+ # -- Returns contracts ------------------------------------------------------
66
+
67
+ class Foo
68
+ +Returns(2)
69
+ def returns_arg(r)
70
+ r
71
+ end
72
+ end
73
+
57
74
  def test_returns_contract
58
75
  assert_nothing_raised() {
59
76
  foo.returns_arg(2)
@@ -65,6 +82,37 @@ class ContractsTest < Test::Unit::TestCase
65
82
  assert e.backtrace.first.include?("test/contracts_test.rb:")
66
83
  end
67
84
 
85
+ # -- test for call to super -------------------------------------------------
86
+
87
+ class Base
88
+ attr :checked
89
+
90
+ def check
91
+ @checked = true
92
+ end
93
+ end
94
+
95
+ class Foo
96
+ +Returns(true)
97
+ def check
98
+ super
99
+ end
100
+ end
101
+
102
+ def test_calls_super
103
+ foo.check
104
+ assert foo.checked
105
+ end
106
+
107
+ # -- Nothrow contracts ------------------------------------------------------
108
+
109
+ class Foo
110
+ +Nothrow()
111
+ def unexpected_throw_on_one(v)
112
+ raise if v == 1
113
+ end
114
+ end
115
+
68
116
  def test_nothrow_contract
69
117
  assert_nothing_raised() {
70
118
  foo.unexpected_throw_on_one(2)
@@ -76,6 +124,13 @@ class ContractsTest < Test::Unit::TestCase
76
124
  assert e.backtrace.first.include?("test/contracts_test.rb:")
77
125
  end
78
126
 
127
+ class Foo
128
+ +Expects(v: Fixnum)
129
+ def throw_on_one(v)
130
+ raise if v == 1
131
+ end
132
+ end
133
+
79
134
  def test_still_throws_fine
80
135
  assert_nothing_raised() {
81
136
  foo.throw_on_one(2)
@@ -85,4 +140,25 @@ class ContractsTest < Test::Unit::TestCase
85
140
  foo.throw_on_one(1)
86
141
  }
87
142
  end
143
+
144
+ # -- Runtime contracts ------------------------------------------------------
145
+
146
+ require "timecop"
147
+ class Foo
148
+ +Runtime(0.01, max: 0.05)
149
+ def wait_for(time)
150
+ Timecop.travel(Time.now + time)
151
+ end
152
+ end
153
+
154
+ def test_runtime
155
+ foo.wait_for 0.001
156
+ foo.wait_for 0.02
157
+
158
+ e = assert_raise(Contracts::Error) {
159
+ foo.wait_for 10
160
+ }
161
+
162
+ Timecop.return
163
+ end
88
164
  end
@@ -9,7 +9,7 @@ class ExpectationTest < Test::Unit::TestCase
9
9
  end
10
10
 
11
11
  #
12
- # This test covers the usual use case: expectations are
12
+ # This test covers the usual use case: expectations are
13
13
  # passed in a single Hash.
14
14
  def test_hash_expectation
15
15
  assert_expectation! "1" => /1/
@@ -36,6 +36,28 @@ class ExpectationTest < Test::Unit::TestCase
36
36
  assert_failed_expectation! do nil end
37
37
  end
38
38
 
39
+ def test_array_expectations
40
+ assert_expectation! 1 => [Fixnum, String]
41
+ assert_expectation! 1 => [String, Fixnum]
42
+ assert_failed_expectation! 1 => [NilClass, String]
43
+ end
44
+
45
+ def test_multi_expectations
46
+ assert_expectation! 1 => Fixnum | String
47
+ assert_expectation! 1 => String | 1
48
+ assert_failed_expectation! 1 => NilClass | String
49
+ assert_expectation! 1 => NilClass | String | 1
50
+ end
51
+
52
+ module I; end
53
+ class X; include I; end
54
+ class Y; end
55
+
56
+ def test_interface_expectations
57
+ assert_expectation! X.new => I
58
+ assert_failed_expectation! Y.new => I
59
+ end
60
+
39
61
  def test_exception_message
40
62
  e = assert_failed_expectation!({ 1 => 2 })
41
63
  assert e.message.include?("1 does not match 2")
@@ -5,21 +5,23 @@ require_relative 'test_helper'
5
5
 
6
6
  class MatchingTest < Test::Unit::TestCase
7
7
  def assert_match(value, expectation)
8
- assert_equal true, Expectation.match?(value, expectation)
8
+ assert_equal true, Expectation::Matcher.match?(value, expectation)
9
9
  end
10
10
 
11
11
  def assert_mismatch(value, expectation)
12
- assert_equal false, Expectation.match?(value, expectation)
12
+ assert_equal false, Expectation::Matcher.match?(value, expectation)
13
13
  end
14
14
 
15
15
  def test_mismatches_raise_exceptions
16
- assert_nothing_raised do
17
- Expectation.match! 1, 1
18
- end
16
+ assert_match 1, 1
17
+ assert_mismatch 1, 2
18
+ end
19
19
 
20
- assert_raise(Expectation::Error) {
21
- Expectation.match! 1, 2
22
- }
20
+ def test_array_matches
21
+ assert_match [1], [Integer]
22
+ assert_mismatch [1], [String]
23
+ assert_match [1, "2"], [[Integer, String]]
24
+ assert_mismatch [1, "2", /abc/], [[Integer, String]]
23
25
  end
24
26
 
25
27
  def test_int_expectations
data/test/test_helper.rb CHANGED
@@ -1,18 +1,23 @@
1
1
  require 'rubygems'
2
2
  require 'bundler/setup'
3
3
 
4
- require 'simplecov'
5
- require 'simplecov-console'
4
+ if ENV["COVERAGE"]
5
+ require 'simplecov'
6
+ require 'simplecov-console'
7
+ end
8
+
6
9
  require 'test/unit'
7
10
 
8
- SimpleCov.start do
9
- add_filter "test/*.rb"
10
- end
11
+ if ENV["COVERAGE"]
12
+ SimpleCov.start do
13
+ add_filter "test/*.rb"
14
+ end
11
15
 
12
- SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
13
- SimpleCov::Formatter::HTMLFormatter,
14
- SimpleCov::Formatter::Console,
15
- ]
16
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
17
+ SimpleCov::Formatter::HTMLFormatter,
18
+ SimpleCov::Formatter::Console,
19
+ ]
20
+ end
16
21
 
17
22
  require "expectation"
18
23
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: expectation
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - radiospiel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-11-09 00:00:00.000000000 Z
11
+ date: 2015-11-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Defensive programming with expectations
14
14
  email: eno@radiospiel.org
@@ -18,9 +18,15 @@ extra_rdoc_files: []
18
18
  files:
19
19
  - README.md
20
20
  - lib/contracts.rb
21
+ - lib/contracts/expects.rb
22
+ - lib/contracts/nothrows.rb
23
+ - lib/contracts/returns.rb
24
+ - lib/contracts/runtime.rb
21
25
  - lib/core/exception.rb
22
26
  - lib/expectation.rb
23
27
  - lib/expectation/assertions.rb
28
+ - lib/expectation/matcher.rb
29
+ - lib/expectation/multi_matcher.rb
24
30
  - lib/expectation/version.rb
25
31
  - test/assertions_test.rb
26
32
  - test/contracts_module_test.rb