expectation 1.0.0 → 1.1.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.
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