speculation 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  module Speculation
3
- using Speculation::NamespacedSymbols.refine(self)
4
- using Conj
5
-
6
3
  # @private
7
4
  class HashSpec < SpecImpl
5
+ include NamespacedSymbols
8
6
  S = Speculation
9
7
 
10
8
  attr_reader :id
@@ -19,7 +17,8 @@ module Speculation
19
17
  req_keys = req.flat_map(&method(:extract_keys))
20
18
  req_un_specs = req_un.flat_map(&method(:extract_keys))
21
19
 
22
- unless (req_keys + req_un_specs + opt + opt_un).all? { |s| s.is_a?(Symbol) && s.namespace }
20
+ all_keys = req_keys + req_un_specs + opt + opt_un
21
+ unless all_keys.all? { |s| s.is_a?(Symbol) && NamespacedSymbols.namespace(s) }
23
22
  raise "all keys must be namespaced Symbols"
24
23
  end
25
24
 
@@ -27,8 +26,8 @@ module Speculation
27
26
  req_keys += req_un_specs.map(&method(:unqualify_key))
28
27
 
29
28
  pred_exprs = [Utils.method(:hash?)]
30
- pred_exprs.push(->(v) { parse_req(req, v, :itself.to_proc) == true }) if req.any?
31
- pred_exprs.push(->(v) { parse_req(req_un, v, method(:unqualify_key)) == true }) if req_un.any?
29
+ pred_exprs.push(->(v) { parse_req(req, v, Utils.method(:itself)).empty? }) if req.any?
30
+ pred_exprs.push(->(v) { parse_req(req_un, v, method(:unqualify_key)).empty? }) if req_un.any?
32
31
 
33
32
  @req_keys = req_keys
34
33
  @req_specs = req_specs
@@ -39,7 +38,7 @@ module Speculation
39
38
  end
40
39
 
41
40
  def conform(value)
42
- return :invalid.ns unless @keys_pred.call(value)
41
+ return ns(S, :invalid) unless @keys_pred.call(value)
43
42
 
44
43
  reg = S.registry
45
44
  ret = value
@@ -53,7 +52,7 @@ module Speculation
53
52
  conformed_value = S.conform(spec, v)
54
53
 
55
54
  if S.invalid?(conformed_value)
56
- return :invalid.ns
55
+ return ns(S, :invalid)
57
56
  else
58
57
  unless conformed_value.equal?(v)
59
58
  ret = ret.merge(key => conformed_value)
@@ -66,30 +65,27 @@ module Speculation
66
65
 
67
66
  def explain(path, via, inn, value)
68
67
  unless Utils.hash?(value)
69
- return [{ :path => path, :pred => :hash?, :val => value, :via => via, :in => inn }]
68
+ return [{ :path => path, :pred => [Utils.method(:hash?), [value]], :val => value, :via => via, :in => inn }]
70
69
  end
71
70
 
72
71
  problems = []
73
72
 
74
73
  if @req.any?
75
- valid_or_failure = parse_req(@req, value, :itself.to_proc)
74
+ failures = parse_req(@req, value, Utils.method(:itself))
76
75
 
77
- unless valid_or_failure == true
78
- valid_or_failure.each do |failure_sexp|
79
- pred = sexp_to_rb(failure_sexp)
80
- problems << { :path => path, :pred => pred, :val => value, :via => via, :in => inn }
81
- end
76
+ failures.each do |failure_sexp|
77
+ # eww
78
+ pred = [Utils.method(:key?), [sexp_to_rb(failure_sexp)]]
79
+ problems << { :path => path, :pred => pred, :val => value, :via => via, :in => inn }
82
80
  end
83
81
  end
84
82
 
85
83
  if @req_un.any?
86
- valid_or_failure = parse_req(@req_un, value, method(:unqualify_key))
84
+ failures = parse_req(@req_un, value, method(:unqualify_key))
87
85
 
88
- unless valid_or_failure == true
89
- valid_or_failure.each do |failure_sexp|
90
- pred = sexp_to_rb(failure_sexp)
91
- problems << { :path => path, :pred => pred, :val => value, :via => via, :in => inn }
92
- end
86
+ failures.each do |failure_sexp|
87
+ pred = [Utils.method(:key?), [sexp_to_rb(failure_sexp)]]
88
+ problems << { :path => path, :pred => pred, :val => value, :via => via, :in => inn }
93
89
  end
94
90
  end
95
91
 
@@ -97,7 +93,7 @@ module Speculation
97
93
  next unless S.registry.key?(@key_to_spec_map[k])
98
94
 
99
95
  unless S.pvalid?(@key_to_spec_map.fetch(k), v)
100
- S.explain1(@key_to_spec_map.fetch(k), path.conj(k), via, inn.conj(k), v)
96
+ S.explain1(@key_to_spec_map.fetch(k), Utils.conj(path, k), via, Utils.conj(inn, k), v)
101
97
  end
102
98
  }
103
99
 
@@ -115,7 +111,7 @@ module Speculation
115
111
 
116
112
  reqs = @req_keys.zip(@req_specs).
117
113
  reduce({}) { |m, (k, s)|
118
- m.merge(k => S.gensub(s, overrides, path.conj(k), rmap))
114
+ m.merge(k => S.gensub(s, overrides, Utils.conj(path, k), rmap))
119
115
  }
120
116
 
121
117
  opts = @opt_keys.zip(@opt_specs).
@@ -123,13 +119,13 @@ module Speculation
123
119
  if S.recur_limit?(rmap, @id, path, k)
124
120
  m
125
121
  else
126
- m.merge(k => Gen.delay { S.gensub(s, overrides, path.conj(k), rmap) })
122
+ m.merge(k => Gen.delay { S.gensub(s, overrides, Utils.conj(path, k), rmap) })
127
123
  end
128
124
  }
129
125
 
130
126
  ->(rantly) do
131
127
  count = rantly.range(0, opts.count)
132
- opts = opts.to_a.shuffle.take(count).to_h
128
+ opts = Hash[opts.to_a.shuffle.take(count)]
133
129
 
134
130
  reqs.merge(opts).each_with_object({}) { |(k, spec_gen), h|
135
131
  h[k] = spec_gen.call(rantly)
@@ -148,17 +144,17 @@ module Speculation
148
144
 
149
145
  keys.each_with_index do |key, i|
150
146
  unless i.zero?
151
- rb_string << " #{op.name} "
147
+ rb_string << " #{NamespacedSymbols.name(op)} "
152
148
  end
153
149
 
154
- rb_string << sexp_to_rb(key, level + 1)
150
+ rb_string << sexp_to_rb(key, level + 1).to_s
155
151
  end
156
152
 
157
153
  rb_string << ")" unless level.zero?
158
154
 
159
155
  rb_string
160
156
  else
161
- ":#{sexp}"
157
+ sexp
162
158
  end
163
159
  end
164
160
 
@@ -171,7 +167,7 @@ module Speculation
171
167
  end
172
168
 
173
169
  def unqualify_key(x)
174
- x.name.to_sym
170
+ NamespacedSymbols.name(x).to_sym
175
171
  end
176
172
 
177
173
  def parse_req(ks, v, f)
@@ -180,15 +176,15 @@ module Speculation
180
176
  ret = if key.is_a?(Array)
181
177
  op, *kks = key
182
178
  case op
183
- when :or.ns
184
- if kks.one? { |k| parse_req([k], v, f) == true }
185
- true
179
+ when ns(S, :or)
180
+ if kks.one? { |k| parse_req([k], v, f).empty? }
181
+ []
186
182
  else
187
183
  [key]
188
184
  end
189
- when :and.ns
190
- if kks.all? { |k| parse_req([k], v, f) == true }
191
- true
185
+ when ns(S, :and)
186
+ if kks.all? { |k| parse_req([k], v, f).empty? }
187
+ []
192
188
  else
193
189
  [key]
194
190
  end
@@ -196,17 +192,13 @@ module Speculation
196
192
  raise "Expected or, and, got #{op}"
197
193
  end
198
194
  elsif v.key?(f.call(key))
199
- true
195
+ []
200
196
  else
201
197
  [key]
202
198
  end
203
199
 
204
200
  if ks.any?
205
- if ret == true
206
- parse_req(ks, v, f)
207
- else
208
- ret + parse_req(ks, v, f)
209
- end
201
+ ret + parse_req(ks, v, f)
210
202
  else
211
203
  ret
212
204
  end
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  module Speculation
3
- using NamespacedSymbols.refine(self)
4
-
5
3
  # @private
6
4
  class MergeSpec < SpecImpl
5
+ include NamespacedSymbols
7
6
  S = Speculation
8
7
 
9
8
  def initialize(preds)
@@ -14,7 +13,7 @@ module Speculation
14
13
  ms = @preds.map { |pred| S.dt(pred, x) }
15
14
 
16
15
  if ms.any?(&S.method(:invalid?))
17
- :invalid.ns(S)
16
+ ns(S, :invalid)
18
17
  else
19
18
  ms.reduce(&:merge)
20
19
  end
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  module Speculation
3
- using NamespacedSymbols.refine(self)
4
- using Conj
5
-
6
3
  # @private
7
4
  class NilableSpec < SpecImpl
5
+ include NamespacedSymbols
8
6
  S = Speculation
9
7
 
10
8
  def initialize(pred)
@@ -13,15 +11,16 @@ module Speculation
13
11
  end
14
12
 
15
13
  def conform(value)
16
- value.nil? ? value : @delayed_spec.value.conform(value)
14
+ value.nil? ? value : @delayed_spec.value!.conform(value)
17
15
  end
18
16
 
19
17
  def explain(path, via, inn, value)
20
- return if S.pvalid?(@delayed_spec.value, value) || value.nil?
18
+ return if S.pvalid?(@delayed_spec.value!, value) || value.nil?
21
19
 
22
- S.
23
- explain1(@pred, path.conj(:pred.ns), via, inn, value).
24
- conj(:path => path.conj(:nil.ns), :pred => NilClass, :val => value, :via => via, :in => inn)
20
+ Utils.conj(
21
+ S.explain1(@pred, Utils.conj(path, ns(S, :pred)), via, inn, value),
22
+ :path => Utils.conj(path, ns(S, :nil)), :pred => [NilClass, [value]], :val => value, :via => via, :in => inn
23
+ )
25
24
  end
26
25
 
27
26
  def gen(overrides, path, rmap)
@@ -29,7 +28,7 @@ module Speculation
29
28
 
30
29
  ->(rantly) do
31
30
  rantly.freq([1, Gen.delay { Utils.constantly(nil) }],
32
- [9, Gen.delay { S.gensub(@pred, overrides, path.conj(:pred.ns), rmap) }])
31
+ [9, Gen.delay { S.gensub(@pred, overrides, Utils.conj(path, ns(S, :pred)), rmap) }])
33
32
  end
34
33
  end
35
34
  end
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  module Speculation
3
- using NamespacedSymbols.refine(self)
4
- using Conj
5
-
6
3
  # @private
7
4
  class OrSpec < SpecImpl
5
+ include NamespacedSymbols
8
6
  S = Speculation
9
7
 
10
8
  attr_reader :id
@@ -21,7 +19,7 @@ module Speculation
21
19
  end
22
20
 
23
21
  def conform(value)
24
- @delayed_specs.value.each_with_index do |spec, index|
22
+ @delayed_specs.value!.each_with_index do |spec, index|
25
23
  conformed = spec.conform(value)
26
24
 
27
25
  unless S.invalid?(conformed)
@@ -29,7 +27,7 @@ module Speculation
29
27
  end
30
28
  end
31
29
 
32
- :invalid.ns
30
+ ns(S, :invalid)
33
31
  end
34
32
 
35
33
  def explain(path, via, inn, value)
@@ -37,7 +35,7 @@ module Speculation
37
35
 
38
36
  @keys.zip(@preds).flat_map do |(key, pred)|
39
37
  next if S.pvalid?(pred, value)
40
- S.explain1(pred, path.conj(key), via, inn, value)
38
+ S.explain1(pred, Utils.conj(path, key), via, inn, value)
41
39
  end
42
40
  end
43
41
 
@@ -49,7 +47,7 @@ module Speculation
49
47
  rmap = S.inck(rmap, @id)
50
48
 
51
49
  unless S.recur_limit?(rmap, @id, path, k)
52
- Gen.delay { S.gensub(p, overrides, path.conj(k), rmap) }
50
+ Gen.delay { S.gensub(p, overrides, Utils.conj(path, k), rmap) }
53
51
  end
54
52
  }.
55
53
  compact
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  module Speculation
3
- using NamespacedSymbols.refine(self)
4
-
5
3
  # @private
6
4
  class RegexSpec < SpecImpl
5
+ include NamespacedSymbols
7
6
  S = Speculation
8
7
 
9
8
  def initialize(regex)
@@ -14,7 +13,7 @@ module Speculation
14
13
  if value.nil? || Utils.collection?(value)
15
14
  S.re_conform(@regex, value)
16
15
  else
17
- :invalid.ns
16
+ ns(S, :invalid)
18
17
  end
19
18
  end
20
19
 
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  module Speculation
3
- using Speculation::NamespacedSymbols.refine(self)
4
- using Conj
5
-
6
3
  # @private
7
4
  class Spec < SpecImpl
5
+ include NamespacedSymbols
8
6
  S = Speculation
9
7
 
10
8
  def initialize(predicate, should_conform)
@@ -22,13 +20,13 @@ module Speculation
22
20
  if @should_conform
23
21
  ret
24
22
  else
25
- ret ? value : :invalid.ns
23
+ ret ? value : ns(S, :invalid)
26
24
  end
27
25
  end
28
26
 
29
27
  def explain(path, via, inn, value)
30
28
  if S.invalid?(S.dt(@predicate, value))
31
- [{ :path => path, :val => value, :via => via, :in => inn, :pred => @predicate }]
29
+ [{ :path => path, :val => value, :via => via, :in => inn, :pred => [@predicate, [value]] }]
32
30
  end
33
31
  end
34
32
 
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  module Speculation
3
- using Speculation::NamespacedSymbols.refine(self)
4
- using Conj
5
-
6
3
  # @private
7
4
  class TupleSpec < SpecImpl
5
+ include NamespacedSymbols
8
6
  S = Speculation
9
7
 
10
8
  def initialize(preds)
@@ -16,10 +14,10 @@ module Speculation
16
14
  end
17
15
 
18
16
  def conform(collection)
19
- specs = @delayed_specs.value
17
+ specs = @delayed_specs.value!
20
18
 
21
19
  unless Utils.array?(collection) && collection.count == specs.count
22
- return :invalid.ns
20
+ return ns(S, :invalid)
23
21
  end
24
22
 
25
23
  return_value = collection.class.new
@@ -28,7 +26,7 @@ module Speculation
28
26
  conformed_value = spec.conform(value)
29
27
 
30
28
  if S.invalid?(conformed_value)
31
- return :invalid.ns
29
+ return ns(S, :invalid)
32
30
  else
33
31
  return_value += [conformed_value]
34
32
  end
@@ -39,13 +37,13 @@ module Speculation
39
37
 
40
38
  def explain(path, via, inn, value)
41
39
  if !Utils.array?(value)
42
- [{ :path => path, :val => value, :via => via, :in => inn, :pred => "array?" }]
40
+ [{ :path => path, :val => value, :via => via, :in => inn, :pred => [Utils.method(:array?), [value]] }]
43
41
  elsif @preds.count != value.count
44
- [{ :path => path, :val => value, :via => via, :in => inn, :pred => "count == predicates.count" }]
42
+ [{ :path => path, :val => value, :via => via, :in => inn, :pred => [Utils.method(:count_eq), [@preds, value.count]] }]
45
43
  else
46
44
  probs = @preds.zip(value).each_with_index.flat_map { |(pred, x), index|
47
45
  unless S.pvalid?(pred, x)
48
- S.explain1(pred, path.conj(index), via, inn.conj(index), x)
46
+ S.explain1(pred, Utils.conj(path, index), via, Utils.conj(inn, index), x)
49
47
  end
50
48
  }
51
49
 
@@ -57,7 +55,7 @@ module Speculation
57
55
  return @gen if @gen
58
56
 
59
57
  gens = @preds.each_with_index.
60
- map { |p, i| S.gensub(p, overrides, path.conj(i), rmap) }
58
+ map { |p, i| S.gensub(p, overrides, Utils.conj(path, i), rmap) }
61
59
 
62
60
  ->(rantly) do
63
61
  gens.map { |g| g.call(rantly) }
@@ -2,11 +2,12 @@
2
2
  require "concurrent"
3
3
  require "pp"
4
4
  require "speculation/pmap"
5
+ require "speculation/gen"
5
6
 
6
7
  module Speculation
7
8
  module Test
8
- using NamespacedSymbols.refine(self)
9
- using Pmap
9
+ extend NamespacedSymbols
10
+ extend Pmap
10
11
 
11
12
  # @private
12
13
  S = Speculation
@@ -27,8 +28,8 @@ module Speculation
27
28
  instrument_enabled.value = true
28
29
  end
29
30
 
30
- # Given an opts hash as per instrument, returns the set of
31
- # Speculation::Identifiers for methods that can be instrumented.
31
+ # Given an opts hash as per instrument, returns the set of methods that can
32
+ # be instrumented.
32
33
  # @param opts [Hash]
33
34
  # @return [Array<Identifier>]
34
35
  def self.instrumentable_methods(opts = {})
@@ -38,16 +39,16 @@ module Speculation
38
39
  end
39
40
  end
40
41
 
41
- S.registry.keys.select(&method(:fn_spec_name?)).to_set.tap do |set|
42
+ S.registry.keys.select(&method(:fn_spec_name?)).to_set.tap { |set|
42
43
  set.merge(opts[:spec].keys) if opts[:spec]
43
44
  set.merge(opts[:stub]) if opts[:stub]
44
45
  set.merge(opts[:replace].keys) if opts[:replace]
45
- end
46
+ }.map(&method(:Method))
46
47
  end
47
48
 
48
- # @param method_or_methods [Method, Identifier, Array<Method>, Array<Identifier>]
49
+ # @param method_or_methods [Method, Array<Method>]
49
50
  # Instruments the methods named by method-or-methods, a method or collection
50
- # of methods, or all instrumentable methods if method-or-methods is not
51
+ # of methods, or all instrumentable methods if method_or_methods is not
51
52
  # specified.
52
53
  # If a method has an :args fn-spec, replaces the method with a method that
53
54
  # checks arg conformance (throwing an exception on failure) before
@@ -74,7 +75,7 @@ module Speculation
74
75
  # then invokes the method/proc you provide, enabling arbitrary stubbing and
75
76
  # mocking.
76
77
  #
77
- # @return [Array<Identifier>] a collection of Identifiers naming the methods instrumented.
78
+ # @return [Array<Method>] a collection of methods instrumented.
78
79
  def self.instrument(method_or_methods = instrumentable_methods, opts = {})
79
80
  if opts[:gen]
80
81
  gens = opts[:gen].reduce({}) { |h, (k, v)| h.merge(S.Identifier(k) => v) }
@@ -85,24 +86,26 @@ module Speculation
85
86
  map { |method| S.Identifier(method) }.
86
87
  uniq.
87
88
  map { |ident| instrument1(ident, opts) }.
88
- compact
89
+ compact.
90
+ map(&method(:Method))
89
91
  end
90
92
 
91
93
  # Undoes instrument on the method_or_methods, specified as in instrument.
92
94
  # With no args, unstruments all instrumented methods.
93
- # @param method_or_methods [Method, Identifier, Array<Method>, Array<Identifier>]
94
- # @return [Array<Identifier>] a collection of Identifiers naming the methods unstrumented
95
+ # @param method_or_methods [Method, Array<Method>]
96
+ # @return [Array<Method>] a collection of methods unstrumented
95
97
  def self.unstrument(method_or_methods = nil)
96
98
  method_or_methods ||= @instrumented_methods.value.keys
97
99
 
98
100
  Array(method_or_methods).
99
101
  map { |method| S.Identifier(method) }.
100
102
  map { |ident| unstrument1(ident) }.
101
- compact
103
+ compact.
104
+ map(&method(:Method))
102
105
  end
103
106
 
104
107
  # Runs generative tests for method using spec and opts.
105
- # @param method [Method, Identifier]
108
+ # @param method [Method]
106
109
  # @param spec [Spec]
107
110
  # @param opts [Hash]
108
111
  # @return [Hash]
@@ -113,17 +116,16 @@ module Speculation
113
116
  end
114
117
 
115
118
  # @param opts [Hash] an opts hash as per `check`
116
- # @return [Array<Identifier>] the set of Identifiers that can be checked.
119
+ # @return [Array<Method>] the array of methods that can be checked.
117
120
  def self.checkable_methods(opts = {})
118
121
  validate_check_opts(opts)
119
122
 
120
123
  S.
121
124
  registry.
122
125
  keys.
123
- select(&method(:fn_spec_name?)).
124
- reject(&:instance_method?).
125
- to_set.
126
- tap { |set| set.merge(opts[:spec].keys) if opts[:spec] }
126
+ select { |k| fn_spec_name?(k) && !k.instance_method? }.
127
+ concat(Hash(opts[:spec]).keys).
128
+ map(&method(:Method))
127
129
  end
128
130
 
129
131
  # Run generative tests for spec conformance on method_or_methods. If
@@ -151,10 +153,15 @@ module Speculation
151
153
  def self.check(method_or_methods = nil, opts = {})
152
154
  method_or_methods ||= checkable_methods
153
155
 
154
- Array(method_or_methods).
155
- map { |method| S.Identifier(method) }.
156
- select { |ident| checkable_methods(opts).include?(ident) }.
157
- pmap { |ident| check1(ident, S.get_spec(ident), opts) }
156
+ checkable = Set(checkable_methods(opts))
157
+ checkable.map!(&S.method(:Identifier))
158
+
159
+ methods = Set(method_or_methods)
160
+ methods.map!(&S.method(:Identifier))
161
+
162
+ pmap(methods.intersection(checkable)) { |ident|
163
+ check1(ident, S.get_spec(ident), opts)
164
+ }
158
165
  end
159
166
 
160
167
  # Given a check result, returns an abbreviated version suitable for summary use.
@@ -162,11 +169,11 @@ module Speculation
162
169
  # @return [Hash]
163
170
  def self.abbrev_result(x)
164
171
  if x[:failure]
165
- x.reject { |k, _| k == :ret.ns }.
172
+ x.reject { |k, _| k == ns(:ret) }.
166
173
  merge(:spec => x[:spec].inspect,
167
174
  :failure => unwrap_failure(x[:failure]))
168
175
  else
169
- x.reject { |k, _| [:spec, :ret.ns].include?(k) }
176
+ x.reject { |k, _| [:spec, ns(:ret)].include?(k) }
170
177
  end
171
178
  end
172
179
 
@@ -194,6 +201,13 @@ module Speculation
194
201
  }
195
202
  end
196
203
 
204
+ # @param modules [Module, Class]
205
+ # @return [Array<Method>] an array of public and protected singleton
206
+ # methods belonging to modules
207
+ def self.enumerate_methods(*modules)
208
+ modules.flat_map { |mod| mod.methods(false).map(&mod.method(:method)) } # method
209
+ end
210
+
197
211
  class << self
198
212
  private
199
213
 
@@ -201,27 +215,27 @@ module Speculation
201
215
  fspec = S.send(:maybe_spec, fspec)
202
216
 
203
217
  conform = ->(args, block) do
204
- conformed_args = S.conform(fspec.argspec, args)
205
- conformed_block = S.conform(fspec.blockspec, block) if fspec.blockspec
218
+ conformed_args = S.conform(fspec.args, args)
219
+ conformed_block = S.conform(fspec.block, block) if fspec.block
206
220
 
207
- if conformed_args == :invalid.ns(S)
221
+ if conformed_args == ns(S, :invalid)
208
222
  backtrace = backtrace_relevant_to_instrument(caller)
209
223
 
210
224
  ed = S.
211
- _explain_data(fspec.argspec, [:args], [], [], args).
212
- merge(:args.ns(S) => args, :failure.ns(S) => :instrument, :caller.ns => backtrace)
225
+ _explain_data(fspec.args, [:args], [], [], args).
226
+ merge(ns(S, :args) => args, ns(S, :failure) => :instrument, ns(:caller) => backtrace.first)
213
227
 
214
228
  io = StringIO.new
215
229
  S.explain_out(ed, io)
216
230
  msg = io.string
217
231
 
218
232
  raise Speculation::Error.new("Call to '#{ident}' did not conform to spec:\n #{msg}", ed)
219
- elsif conformed_block == :invalid.ns(S)
233
+ elsif conformed_block == ns(S, :invalid)
220
234
  backtrace = backtrace_relevant_to_instrument(caller)
221
235
 
222
236
  ed = S.
223
- _explain_data(fspec.blockspec, [:block], [], [], block).
224
- merge(:block.ns(S) => block, :failure.ns(S) => :instrument, :caller.ns => backtrace)
237
+ _explain_data(fspec.block, [:block], [], [], block).
238
+ merge(ns(S, :block) => block, ns(S, :failure) => :instrument, ns(:caller) => backtrace.first)
225
239
 
226
240
  io = StringIO.new
227
241
  S.explain_out(ed, io)
@@ -236,7 +250,7 @@ module Speculation
236
250
 
237
251
  if Test.instrument_enabled.value
238
252
  Test.with_instrument_disabled do
239
- conform.call(args, block) if fspec.argspec
253
+ conform.call(args, block) if fspec.args
240
254
 
241
255
  begin
242
256
  Test.instrument_enabled.value = true
@@ -252,7 +266,7 @@ module Speculation
252
266
  end
253
267
 
254
268
  def no_fspec(ident, spec)
255
- S::Error.new("#{ident} not spec'ed", :method => ident, :spec => spec, :failure.ns(S) => :no_fspec)
269
+ S::Error.new("#{ident} not spec'ed", :method => ident, :spec => spec, ns(S, :failure) => :no_fspec)
256
270
  end
257
271
 
258
272
  def instrument1(ident, opts)
@@ -324,9 +338,9 @@ module Speculation
324
338
  def explain_check(args, spec, v, role)
325
339
  data = unless S.valid?(spec, v)
326
340
  S._explain_data(spec, [role], [], [], v).
327
- merge(:args.ns => args,
328
- :val.ns => v,
329
- :failure.ns(S) => :check_failed)
341
+ merge(ns(:args) => args,
342
+ ns(:val) => v,
343
+ ns(S, :failure) => :check_failed)
330
344
  end
331
345
 
332
346
  S::Error.new("Specification-based check failed", data).tap do |e|
@@ -338,32 +352,32 @@ module Speculation
338
352
  # :backtrace, :cause and :data keys. :data will have a
339
353
  # :"Speculation/failure" key.
340
354
  def check_call(method, spec, args, block)
341
- conformed_args = S.conform(spec.argspec, args) if spec.argspec
355
+ conformed_args = S.conform(spec.args, args) if spec.args
342
356
 
343
- if conformed_args == :invalid.ns(S)
344
- return explain_check(args, spec.argspec, args, :args)
357
+ if conformed_args == ns(S, :invalid)
358
+ return explain_check(args, spec.args, args, :args)
345
359
  end
346
360
 
347
- conformed_block = S.conform(spec.blockspec, block) if spec.blockspec
361
+ conformed_block = S.conform(spec.block, block) if spec.block
348
362
 
349
- if conformed_block == :invalid.ns(S)
363
+ if conformed_block == ns(S, :invalid)
350
364
  return explain_check(block, spec.block, block, :block)
351
365
  end
352
366
 
353
367
  ret = method.call(*args, &block)
354
368
 
355
- conformed_ret = S.conform(spec.retspec, ret) if spec.retspec
369
+ conformed_ret = S.conform(spec.ret, ret) if spec.ret
356
370
 
357
- if conformed_ret == :invalid.ns(S)
358
- return explain_check(args, spec.retspec, ret, :ret)
371
+ if conformed_ret == ns(S, :invalid)
372
+ return explain_check(args, spec.ret, ret, :ret)
359
373
  end
360
374
 
361
- return true unless spec.argspec && spec.retspec && spec.fnspec
375
+ return true unless spec.args && spec.ret && spec.fn
362
376
 
363
- if S.valid?(spec.fnspec, :args => conformed_args, :block => conformed_block, :ret => conformed_ret)
377
+ if S.valid?(spec.fn, :args => conformed_args, :block => conformed_block, :ret => conformed_ret)
364
378
  true
365
379
  else
366
- explain_check(args, spec.fnspec, { :args => conformed_args, :block => conformed_block, :ret => conformed_ret }, :fn)
380
+ explain_check(args, spec.fn, { :args => conformed_args, :block => conformed_block, :ret => conformed_ret }, :fn)
367
381
  end
368
382
  end
369
383
 
@@ -372,14 +386,14 @@ module Speculation
372
386
  num_tests = opts.fetch(:num_tests, 1000)
373
387
 
374
388
  args_gen = begin
375
- S.gen(spec.argspec, gen)
389
+ S.gen(spec.args, gen)
376
390
  rescue => e
377
391
  return { :result => e }
378
392
  end
379
393
 
380
- block_gen = if spec.blockspec
394
+ block_gen = if spec.block
381
395
  begin
382
- S.gen(spec.blockspec, gen)
396
+ S.gen(spec.block, gen)
383
397
  rescue => e
384
398
  return { :result => e }
385
399
  end
@@ -387,15 +401,18 @@ module Speculation
387
401
  Utils.constantly(nil)
388
402
  end
389
403
 
390
- combined_gen = ->(r) { [args_gen.call(r), block_gen.call(r)] }
404
+ arg_block_gen = ->(r) { [args_gen.call(r), block_gen.call(r)] }
391
405
 
392
- rantly_quick_check(combined_gen, num_tests) { |(args, block)| check_call(method, spec, args, block) }
406
+ generator_guard = ->(genned_val) { S.valid?(spec.args, genned_val) }
407
+ rantly_quick_check(arg_block_gen, num_tests, generator_guard) do |(args, block)|
408
+ check_call(method, spec, args, block)
409
+ end
393
410
  end
394
411
 
395
412
  def make_check_result(method, spec, check_result)
396
- result = { :spec => spec,
397
- :ret.ns => check_result,
398
- :method => method }
413
+ result = { :spec => spec,
414
+ ns(:ret) => check_result,
415
+ :method => method }
399
416
 
400
417
  if check_result[:result] && check_result[:result] != true
401
418
  result[:failure] = check_result[:result]
@@ -414,12 +431,12 @@ module Speculation
414
431
  reinstrument = unstrument(ident).any?
415
432
  method = ident.get_method
416
433
 
417
- if specd.argspec # or blockspec?
434
+ if specd.args
418
435
  check_result = quick_check(method, spec, opts)
419
436
  make_check_result(method, spec, check_result)
420
437
  else
421
- failure = { :info => "No :args spec",
422
- failure.ns(S) => :no_args_spec }
438
+ failure = { :info => "No :args spec",
439
+ ns(:failure) => :no_args_spec }
423
440
 
424
441
  { :failure => failure,
425
442
  :method => method,
@@ -447,7 +464,7 @@ module Speculation
447
464
 
448
465
  # Reimplementation of Rantly's `check` since it does not provide direct access to results
449
466
  # (shrunk data etc.), instead printing them to STDOUT.
450
- def rantly_quick_check(gen, num_tests)
467
+ def rantly_quick_check(gen, num_tests, generator_guard, &invariant)
451
468
  i = 0
452
469
  limit = 100
453
470
 
@@ -455,32 +472,19 @@ module Speculation
455
472
  args, blk = val
456
473
  i += 1
457
474
 
458
- result = begin
459
- yield([args, blk])
460
- rescue => e
461
- e
462
- end
475
+ result = yield([args, blk]) rescue $!
463
476
 
464
477
  unless result == true
465
- # This is a Rantly Tuple.
466
- args = ::Tuple.new(args)
467
-
468
- if args.respond_to?(:shrink)
469
- shrunk = shrink(args, result, ->(v) { yield([v, blk]) })
470
-
471
- shrunk[:smallest] = [shrunk[:smallest].array, blk]
472
-
473
- return { :fail => args.array,
474
- :block => blk,
475
- :num_tests => i,
476
- :result => result,
477
- :shrunk => shrunk }
478
- else
479
- return { :fail => args.array,
480
- :block => blk,
481
- :num_tests => i,
482
- :result => result }
483
- end
478
+ args = ::Tuple.new(args) # This is a Rantly Tuple.
479
+
480
+ shrunk = shrink(generator_guard, args, result, ->(v) { invariant.call([v, blk]) })
481
+
482
+ shrunk[:smallest] = { :args => shrunk[:smallest].array, :block => blk }
483
+
484
+ return { :fail => { :args => args.array, :block => blk },
485
+ :num_tests => i,
486
+ :result => result,
487
+ :shrunk => shrunk }
484
488
  end
485
489
  end
486
490
 
@@ -489,32 +493,36 @@ module Speculation
489
493
  end
490
494
 
491
495
  # reimplementation of Rantly's shrinking.
492
- def shrink(data, result, block, depth = 0, iteration = 0)
493
- smallest = data
496
+ def shrink(generator_guard, value, result, invariant, depth = 0, iteration = 0)
497
+ smallest = value
494
498
  max_depth = depth
495
499
 
496
- if data.shrinkable?
500
+ if value.shrinkable?
497
501
  while iteration < 1024
498
- shrunk_data = data.shrink
499
- result = begin
500
- block.call(shrunk_data.array)
501
- rescue => e
502
- e
503
- end
502
+ shrunk_value = value.shrink
503
+
504
+ unless generator_guard.call(shrunk_value.array)
505
+ iteration += 1
506
+ value = shrunk_value
507
+ value.shrinkable? ? next : break
508
+ end
504
509
 
505
- unless result == true
506
- shrunk = shrink(shrunk_data, result, block, depth + 1, iteration + 1)
510
+ res = invariant.call(shrunk_value.array) rescue $!
507
511
 
508
- branch_smallest, branch_depth, iteration =
509
- shrunk.values_at(:smallest, :depth, :iteration)
512
+ unless res == true
513
+ shrunk = shrink(generator_guard, shrunk_value, res, invariant, depth + 1, iteration + 1)
514
+
515
+ branch_smallest, branch_depth, iteration, branch_result =
516
+ shrunk.values_at(:smallest, :depth, :iteration, :result)
510
517
 
511
518
  if branch_depth > max_depth
512
- smallest = branch_smallest
513
519
  max_depth = branch_depth
520
+ smallest = branch_smallest
521
+ result = branch_result
514
522
  end
515
523
  end
516
524
 
517
- break unless data.retry?
525
+ break unless value.retry?
518
526
  end
519
527
  end
520
528
 
@@ -527,7 +535,7 @@ module Speculation
527
535
  ### check reporting ###
528
536
 
529
537
  def failure_type(x)
530
- x.data[:failure.ns(S)] if x.is_a?(S::Error)
538
+ x.data[ns(S, :failure)] if x.is_a?(S::Error)
531
539
  end
532
540
 
533
541
  def unwrap_failure(x)
@@ -548,6 +556,23 @@ module Speculation
548
556
  failure_type(failure) || :check_raised
549
557
  end
550
558
  end
559
+
560
+ # if x is an Identifier, return its method
561
+ def Method(x)
562
+ case x
563
+ when Identifier then x.get_method
564
+ when Method, UnboundMethod then x
565
+ else raise ArgumentError, "unexpected method-like object #{x}"
566
+ end
567
+ end
568
+
569
+ def Set(x)
570
+ case x
571
+ when Set then x
572
+ when Enumerable then Set.new(x)
573
+ else Set[x]
574
+ end
575
+ end
551
576
  end
552
577
  end
553
578
  end