speculation 0.1.0 → 0.2.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.
@@ -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