Module: Speculation

Extended by:
NamespacedSymbols
Defined in:
lib/speculation.rb,
lib/speculation/gen.rb,
lib/speculation/pmap.rb,
lib/speculation/test.rb,
lib/speculation/error.rb,
lib/speculation/utils.rb,
lib/speculation/version.rb,
lib/speculation/spec_impl.rb,
lib/speculation/identifier.rb,
lib/speculation/spec_impl/spec.rb,
lib/speculation/spec_impl/f_spec.rb,
lib/speculation/spec_impl/or_spec.rb,
lib/speculation/namespaced_symbols.rb,
lib/speculation/spec_impl/and_spec.rb,
lib/speculation/spec_impl/hash_spec.rb,
lib/speculation/spec_impl/every_spec.rb,
lib/speculation/spec_impl/merge_spec.rb,
lib/speculation/spec_impl/regex_spec.rb,
lib/speculation/spec_impl/tuple_spec.rb,
lib/speculation/spec_impl/nilable_spec.rb

Defined Under Namespace

Modules: Gen, NamespacedSymbols, Test Classes: Error

Constant Summary collapse

VERSION =
"0.2.0"

Class Attribute Summary collapse

Class Method Summary collapse

Methods included from NamespacedSymbols

name, namespace, ns, symbol

Class Attribute Details

.check_assertsObject

Enables or disables spec asserts. Defaults to false.



18
19
20
# File 'lib/speculation.rb', line 18

def check_asserts
  @check_asserts
end

.coll_check_limitObject

The number of elements validated in a collection spec’ed with ‘every’.



30
31
32
# File 'lib/speculation.rb', line 30

def coll_check_limit
  @coll_check_limit
end

.coll_error_limitObject

The number of errors reported by explain in a collection spec’ed with ‘every’



34
35
36
# File 'lib/speculation.rb', line 34

def coll_error_limit
  @coll_error_limit
end

.fspec_iterationsObject

The number of times an anonymous fn specified by fspec will be (generatively) tested during conform.



27
28
29
# File 'lib/speculation.rb', line 27

def fspec_iterations
  @fspec_iterations
end

.recursion_limitObject

A soft limit on how many times a branching spec (or/alt/zero_or_more) can be recursed through during generation. After this a non-recursive branch will be chosen.



23
24
25
# File 'lib/speculation.rb', line 23

def recursion_limit
  @recursion_limit
end

Class Method Details

.alt(kv_specs) ⇒ Hash

Returns regex op that returns a two item array containing the key of the first matching pred and the corresponding value. Thus can be destructured to refer generically to the components of the return.

Examples:

S.alt(even: :even?.to_proc, small: -> (n) { n < 42 })

Parameters:

  • kv_specs (Hash)

    key+pred pairs

Returns:

  • (Hash)

    regex op that returns a two item array containing the key of the first matching pred and the corresponding value. Thus can be destructured to refer generically to the components of the return.



510
511
512
# File 'lib/speculation.rb', line 510

def self.alt(kv_specs)
  _alt(kv_specs.values, kv_specs.keys).merge(:id => SecureRandom.uuid)
end

.and(*preds) ⇒ Spec

Returns a spec that returns the conformed value. Successive conformed values propagate through rest of predicates.

Examples:

S.and(Numeric, -> (n) { n < 42 })

Parameters:

  • preds (Array)

    predicate/specs

Returns:

  • (Spec)

    a spec that returns the conformed value. Successive conformed values propagate through rest of predicates.



392
393
394
# File 'lib/speculation.rb', line 392

def self.and(*preds)
  AndSpec.new(preds)
end

.and_keys(*ks) ⇒ Object

See Also:



373
374
375
# File 'lib/speculation.rb', line 373

def self.and_keys(*ks)
  [ns(:and), *ks]
end

.assert(spec, x) ⇒ Object

Can be enabled or disabled at runtime:

  • enabled/disabled by setting check_asserts.

  • enabled by setting environment variable SPECULATION_CHECK_ASSERTS to the string “true”

Defaults to false if not set.

Parameters:

  • spec (Spec)
  • x

    value to validate

Returns:

  • x if x is valid? according to spec

Raises:

  • (Error)

    with explain_data plus :Speculation/failure of :assertion_failed



54
55
56
57
58
59
60
61
62
63
# File 'lib/speculation.rb', line 54

def self.assert(spec, x)
  return x unless check_asserts
  return x if valid?(spec, x)

  ed = _explain_data(spec, [], [], [], x).merge(ns(:failure) => :assertion_failed)
  out = StringIO.new
  explain_out(ed, out)

  raise Speculation::Error.new("Spec assertion failed\n#{out.string}", ed)
end

.cat(named_specs) ⇒ Hash

Returns regex op that matches (all) values in sequence, returning a map containing the keys of each pred and the corresponding value.

Examples:

S.cat(e: :even?.to_proc, o: :odd?.to_proc)

Parameters:

  • named_specs (Hash)

    key+pred hash

Returns:

  • (Hash)

    regex op that matches (all) values in sequence, returning a map containing the keys of each pred and the corresponding value.



519
520
521
522
523
524
# File 'lib/speculation.rb', line 519

def self.cat(named_specs)
  keys = named_specs.keys
  predicates = named_specs.values

  pcat(:keys => keys, :predicates => predicates, :return_value => {})
end

.coll_of(pred, opts = {}) ⇒ Spec

Returns a spec for a collection of items satisfying pred. Unlike ‘every’, coll_of will exhaustively conform every value.

Same options as ‘every’. conform will produce a collection corresponding to :into if supplied, else will match the input collection, avoiding rebuilding when possible.

Parameters:

  • pred
  • opts (Hash) (defaults to: {})

Returns:

  • (Spec)

See Also:



460
461
462
# File 'lib/speculation.rb', line 460

def self.coll_of(pred, opts = {})
  every(pred, ns(:conform_all) => true, **opts)
end

.conform(spec, value) ⇒ Symbol, Object

Returns :Speculation/invalid if value does not match spec, else the (possibly destructured) value.

Parameters:

  • spec (Spec)
  • value

    value to conform

Returns:

  • (Symbol, Object)

    :Speculation/invalid if value does not match spec, else the (possibly destructured) value



129
130
131
132
# File 'lib/speculation.rb', line 129

def self.conform(spec, value)
  spec = Identifier(spec)
  specize(spec).conform(value)
end

.conformer {|value| ... } ⇒ Spec

Returns a spec that uses block as a predicate/conformer.

Yields:

  • (value)

    predicate function with the semantics of conform i.e. it should return either a (possibly converted) value or :“Speculation/invalid”

Returns:

  • (Spec)

    a spec that uses block as a predicate/conformer.



538
539
540
# File 'lib/speculation.rb', line 538

def self.conformer(&pred)
  spec_impl(pred, true)
end

.constrained(re, *preds) ⇒ Hash

Returns regex-op that consumes input as per re but subjects the resulting value to the conjunction of the predicates, and any conforming they might perform.

Parameters:

  • re (Hash)

    regex op

  • preds (Array)

    predicates

Returns:

  • (Hash)

    regex-op that consumes input as per re but subjects the resulting value to the conjunction of the predicates, and any conforming they might perform.



531
532
533
# File 'lib/speculation.rb', line 531

def self.constrained(re, *preds)
  { ns(:op) => ns(:amp), :p1 => re, :predicates => preds }
end

.date_in(date_range) ⇒ Object

Returns Spec that validates dates in the given range.

Parameters:

  • date_range (Range<Date>)

Returns:

  • Spec that validates dates in the given range



103
104
105
106
# File 'lib/speculation.rb', line 103

def self.date_in(date_range)
  spec(self.and(Date, ->(x) { date_range.cover?(x) }),
       :gen => ->(_) { rand(date_range) })
end

.def(key, spec) ⇒ Symbol, Method

Given a namespace-qualified symbol key, and a spec, spec name, predicate or regex-op makes an entry in the registry mapping key to the spec

Parameters:

  • key (Symbol)

    namespace-qualified symbol

  • spec (Spec, Symbol, Proc, Hash)

    a spec, spec name, predicate or regex-op

Returns:

  • (Symbol, Method)


268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/speculation.rb', line 268

def(key, spec)
  key = Identifier(key)

  unless Utils.ident?(key) && (!key.is_a?(Symbol) || NamespacedSymbols.namespace(key))
    raise ArgumentError, "key must be a namespaced Symbol, e.g. #{ns(:my_spec)}, or a Method"
  end

  spec = if spec?(spec) || regex?(spec) || registry[spec]
           spec
         else
           spec_impl(spec, false)
         end

  @registry_ref.swap do |reg|
    reg.merge(key => with_name(spec, key)).freeze
  end

  key.is_a?(Identifier) ? key.get_method : key
end

.every(pred, opts = {}) ⇒ Spec

Note:

that ‘every’ does not do exhaustive checking, rather it samples coll_check_limit elements. Nor (as a result) does it do any conforming of elements. ‘explain’ will report at most coll_error_limit problems. Thus ‘every’ should be suitable for potentially large collections.

Returns spec that validates collection elements against pred.

Parameters:

  • pred

    predicate to validate collections with

  • opts (Hash) (defaults to: {})

    Takes several kwargs options that further constrain the collection:

Options Hash (opts):

  • :kind (Object) — default: nil

    a pred/spec that the collection type must satisfy, e.g. Array Note that if :kind is specified and :into is not, this pred must generate in order for every to generate.

  • :count (Integer) — default: nil

    specifies coll has exactly this count

  • :min_count (Integer) — default: nil

    coll has count >= min_count

  • :max_count (Integer) — default: nil

    coll has count <= max_count

  • :distinct (Boolean) — default: nil

    all the elements are distinct

  • :gen_max (Integer) — default: 20

    the maximum coll size to generate

  • :into (Array, Hash, Set) — default: Array

    one of [], {}, Set[], the default collection to generate into (default: empty coll as generated by :kind pred if supplied, else [])

  • :gen (Proc)

    generator proc, which must be a proc of one arg (Rantly instance) that generates a valid value.

Returns:

  • (Spec)

    spec that validates collection elements against pred

See Also:



425
426
427
428
429
430
431
# File 'lib/speculation.rb', line 425

def self.every(pred, opts = {})
  gen = opts.delete(:gen)

  EverySpec.new(pred, opts).tap do |spec|
    spec.gen = gen
  end
end

.every_kv(kpred, vpred, options) ⇒ Spec

Like ‘every’ but takes separate key and val preds and works on associative collections.

Same options as ‘every’, :into defaults to {}

Parameters:

  • kpred

    key pred

  • vpred

    val pred

  • options (Hash)

Returns:

  • (Spec)

    spec that validates associative collections

See Also:



443
444
445
446
447
# File 'lib/speculation.rb', line 443

def self.every_kv(kpred, vpred, options)
  every(tuple(kpred, vpred), ns(:kfn) => ->(_i, v) { v.first },
                             :into    => {},
                             **options)
end

.exercise(spec, n: 10, overrides: {}) ⇒ Array

Generates a number (default 10) of values compatible with spec and maps conform over them, returning a sequence of [val conformed-val] tuples.

Parameters:

  • spec
  • n (Integer) (defaults to: 10)
  • overrides (Hash) (defaults to: {})

    a generator overrides hash as per gen

Returns:

  • (Array)

    an array of [val, conformed_val] tuples

See Also:



622
623
624
625
626
# File 'lib/speculation.rb', line 622

def self.exercise(spec, n: 10, overrides: {})
  Gen.sample(gen(spec, overrides), n).map { |value|
    [value, conform(spec, value)]
  }
end

.exercise_fn(method, n: 10, fspec: nil) ⇒ Array

Exercises the method by applying it to n (default 10) generated samples of its args spec. When fspec is supplied its arg spec is used, and method can be a proc.

Parameters:

  • method (Method)
  • n (Integer) (defaults to: 10)
  • fspec (Spec) (defaults to: nil)

Returns:

  • (Array)

    an arrray of tuples of [args, ret].

Raises:

  • (ArgumentError)


635
636
637
638
639
640
# File 'lib/speculation.rb', line 635

def self.exercise_fn(method, n: 10, fspec: nil)
  fspec ||= get_spec(method)
  raise ArgumentError, "No fspec found for #{method}" unless fspec

  Gen.sample(gen(fspec.args), n).map { |args| [args, method.call(*args)] }
end

.explain(spec, x) ⇒ Object

Given a spec and a value that fails to conform, prints an explaination to STDOUT

Parameters:

  • spec (Spec)
  • x


206
207
208
# File 'lib/speculation.rb', line 206

def self.explain(spec, x)
  explain_out(explain_data(spec, x))
end

.explain_data(spec, x) ⇒ nil, Hash

Given a spec and a value x which ought to conform, returns nil if x conforms, else a hash with at least the key :“Speculation/problems” whose value is a collection of problem-hashes, where problem-hash has at least :path :pred and :val keys describing the predicate and the value that failed at that path.

Parameters:

  • spec (Spec)
  • x

    value which ought to conform

Returns:

  • (nil, Hash)

    nil if x conforms, else a hash with at least the key :Speculation/problems whose value is a collection of problem-hashes, where problem-hash has at least :path :pred and :val keys describing the predicate and the value that failed at that path.



166
167
168
169
170
# File 'lib/speculation.rb', line 166

def self.explain_data(spec, x)
  spec = Identifier(spec)
  name = spec_name(spec)
  _explain_data(spec, [], Array(name), [], x)
end

.explain_out(ed, out = STDOUT) ⇒ Object

Parameters:

  • ed (Hash)

    explain data (per ‘explain_data’)

  • out (IO) (defaults to: STDOUT)

    destination to write explain human readable message to (default STDOUT)



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/speculation.rb', line 174

def self.explain_out(ed, out = STDOUT)
  return out.puts("Success!") unless ed

  ed.fetch(ns(:problems)).each do |prob|
    path, pred, val, reason, via, inn = prob.values_at(:path, :pred, :val, :reason, :via, :in)

    out.print("In: ", inn.to_a.inspect, " ") unless inn.empty?
    out.print("val: ", val.inspect, " fails")
    out.print(" spec: ", via.last.inspect) unless via.empty?
    out.print(" at: ", path.to_a.inspect) unless path.empty?
    out.print(" predicate: ", pred.inspect)
    out.print(", ", reason.inspect) if reason

    prob.each do |k, v|
      unless [:path, :pred, :val, :reason, :via, :in].include?(k)
        out.print("\n\t ", k.inspect, PP.pp(v, String.new))
      end
    end

    out.puts
  end

  ed.each do |k, v|
    out.puts("#{k} #{PP.pp(v, String.new)}") unless k == ns(:problems)
  end

  nil
end

.explain_str(spec, x) ⇒ String

Returns a human readable explaination.

Parameters:

  • spec (Spec)
  • x

    a value that fails to conform

Returns:

  • (String)

    a human readable explaination



213
214
215
216
217
# File 'lib/speculation.rb', line 213

def self.explain_str(spec, x)
  out = StringIO.new
  explain_out(explain_data(spec, x), out)
  out.string
end

.fdef(method, spec) ⇒ Method

Note:

Note that :fn specs require the presence of :args and :ret specs to conform values, and so :fn specs will be ignored if :args or :ret are missing.

Once registered, specs are checked by instrument and tested by the runner Speculation::Test.check

Examples:

to register method specs for the Hash[] method:

S.fdef(Hash.method(:[]),
  args: S.alt(
    hash: Hash,
    array_of_pairs: S.coll_of(S.tuple(ns(S, :any), ns(S, :any)), kind: Array),
    kvs: S.constrained(S.one_or_more(ns(S, :any)), -> (kvs) { kvs.count.even? })
  ),
  ret: Hash
)

Parameters:

  • method (Method)
  • spec (Hash)

Options Hash (spec):

  • :args (Hash)

    regex spec for the method arguments as a list

  • :block (Object)

    an fspec for the method’s block

  • :ret (Object)

    a spec for the method’s return value

  • :fn (Object)

    a spec of the relationship between args and ret - the value passed is { args: conformed_args, block: given_block, ret: conformed_ret } and is expected to contain predicates that relate those values

Returns:

  • (Method)

    the method spec’ed



594
595
596
597
# File 'lib/speculation.rb', line 594

def self.fdef(method, spec)
  self.def(Identifier(method), fspec(spec))
  method
end

.float_in(min: nil, max: nil, infinite: true, nan: true) ⇒ Spec

Returns that validates floats.

Parameters:

  • infinite (Boolean) (defaults to: true)

    whether +/- infinity allowed (default true)

  • nan (Boolean) (defaults to: true)

    whether Flaot::NAN allowed (default true)

  • min (Boolean) (defaults to: nil)

    minimum value (inclusive, default none)

  • max (Boolean) (defaults to: nil)

    maximum value (inclusive, default none)

Returns:

  • (Spec)

    that validates floats



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/speculation.rb', line 70

def self.float_in(min: nil, max: nil, infinite: true, nan: true)
  preds = [Float]
  preds << ->(x) { !x.nan? } unless nan
  preds << ->(x) { !x.infinite? } unless infinite
  preds << ->(x) { x <= max } if max
  preds << ->(x) { x >= min } if min

  min ||= Float::MIN
  max ||= Float::MAX

  gens = [[20, ->(_) { rand(min.to_f..max.to_f) }]]
  gens << [1, ->(r) { r.choose(Float::INFINITY, -Float::INFINITY) }] if infinite
  gens << [1, ->(_) { Float::NAN }] if nan

  spec(self.and(*preds), :gen => ->(rantly) { rantly.freq(*gens) })
end

.fspec(args: nil, ret: nil, fn: nil, block: nil, gen: nil) ⇒ Spec

Takes :args :ret and (optional) :block and :fn kwargs whose values are preds and returns a spec whose conform/explain take a method/proc and validates it using generative testing. The conformed value is always the method itself.

fspecs can generate procs that validate the arguments and fabricate a return value compliant with the :ret spec, ignoring the :fn spec if present.

Parameters:

  • args (defaults to: nil)

    predicate

  • ret (defaults to: nil)

    predicate

  • fn (defaults to: nil)

    predicate

  • block (defaults to: nil)

    predicate

  • gen (Proc) (defaults to: nil)

    generator proc, which must be a proc of one arg (Rantly instance) that generates a valid value.

Returns:

  • (Spec)

See Also:



557
558
559
560
561
# File 'lib/speculation.rb', line 557

def self.fspec(args: nil, ret: nil, fn: nil, block: nil, gen: nil)
  FSpec.new(:args => spec(args), :ret => spec(ret), :fn => spec(fn), :block => spec(block)).tap do |spec|
    spec.gen = gen
  end
end

.gen(spec, overrides = nil) ⇒ Proc

Given a spec, returns the generator for it, or raises if none can be constructed.

Optionally an overrides hash can be provided which should map spec names or paths (array of symbols) to no-arg generator Procs. These will be used instead of the generators at those names/paths. Note that parent generator (in the spec or overrides map) will supersede those of any subtrees. A generator for a regex op must always return a sequential collection (i.e. a generator for Speculation.zero_or_more should return either an empty array or an array with one item in it)

Parameters:

  • spec (Spec)
  • overrides (Hash) (defaults to: nil)

Returns:

  • (Proc)


249
250
251
252
# File 'lib/speculation.rb', line 249

def self.gen(spec, overrides = nil)
  spec = Identifier(spec)
  gensub(spec, overrides, [], ns(:recursion_limit) => recursion_limit)
end

.get_spec(key) ⇒ Spec?

Returns spec registered for key, or nil.

Parameters:

  • key (Symbol, Method)

Returns:

  • (Spec, nil)

    spec registered for key, or nil



296
297
298
# File 'lib/speculation.rb', line 296

def self.get_spec(key)
  registry[Identifier(key)]
end

.hash_of(kpred, vpred, options = {}) ⇒ Spec

Returns a spec for a hash whose keys satisfy kpred and vals satisfy vpred. Unlike ‘every_kv’, hash_of will exhaustively conform every value.

Same options as ‘every’, :kind defaults to Speculation::Utils.hash?, with the addition of:

:conform_keys - conform keys as well as values (default false)

Parameters:

  • kpred

    key pred

  • vpred

    val pred

  • options (Hash) (defaults to: {})

Returns:

  • (Spec)

See Also:



477
478
479
480
481
# File 'lib/speculation.rb', line 477

def self.hash_of(kpred, vpred, options = {})
  every_kv(kpred, vpred, :kind            => Utils.method(:hash?),
                         ns(:conform_all) => true,
                         **options)
end

.int_in(range) ⇒ Object

Returns Spec that validates ints in the given range.

Parameters:

  • range (Range<Integer>)

Returns:

  • Spec that validates ints in the given range



89
90
91
92
# File 'lib/speculation.rb', line 89

def self.int_in(range)
  spec(self.and(Integer, ->(x) { range.include?(x) }),
       :gen => ->(_) { rand(range) })
end

.invalid?(value) ⇒ Boolean

Returns true if value is the result of an unsuccessful conform.

Parameters:

  • value

    return value of a conform call

Returns:

  • (Boolean)

    true if value is the result of an unsuccessful conform



122
123
124
# File 'lib/speculation.rb', line 122

def self.invalid?(value)
  value.equal?(ns(:invalid))
end

.keys(req: [], opt: [], req_un: [], opt_un: [], gen: nil) ⇒ Object

Creates and returns a hash validating spec. :req and :opt are both arrays of namespaced-qualified keywords (e.g. “:MyApp/foo”). The validator will ensure the :req keys are present. The :opt keys serve as documentation and may be used by the generator.

The :req key array supports ‘and_keys’ and ‘or_keys’ for key groups:

S.keys(req: [ns(:x), ns(:y), S.or_keys(ns(:secret), S.and_keys(ns(:user), ns(:pwd)))],
       opt: [ns(:z)])

There are also _un versions of :req and :opt. These allow you to connect unqualified keys to specs. In each case, fully qualfied keywords are passed, which name the specs, but unqualified keys (with the same name component) are expected and checked at conform-time, and generated during gen:

S.keys(req_un: [:"MyApp/x", :"MyApp/y"])

The above says keys :x and :y are required, and will be validated and generated by specs (if they exist) named :“MyApp/x” :“MyApp/y” respectively.

In addition, the values of all namespace-qualified keys will be validated (and possibly destructured) by any registered specs. Note: there is no support for inline value specification, by design.

Parameters:

  • req (Array<Symbol>) (defaults to: [])
  • opt (Array<Symbol>) (defaults to: [])
  • req_un (Array<Symbol>) (defaults to: [])
  • opt_un (Array<Symbol>) (defaults to: [])
  • gen (Proc) (defaults to: nil)

    generator function, which must be a proc of one arg (Rantly instance) that generates a valid value



361
362
363
364
365
# File 'lib/speculation.rb', line 361

def self.keys(req: [], opt: [], req_un: [], opt_un: [], gen: nil)
  HashSpec.new(req, opt, req_un, opt_un).tap do |spec|
    spec.gen = gen
  end
end

.merge(*preds) ⇒ Spec

Note:

Unlike ‘and’, merge can generate maps satisfying the union of the predicates.

Returns a spec that returns a conformed hash satisfying all of the specs.

Parameters:

  • preds (Array)

    hash-validating specs (e.g. ‘keys’ specs)

Returns:

  • (Spec)

    a spec that returns a conformed hash satisfying all of the specs.



399
400
401
# File 'lib/speculation.rb', line 399

def self.merge(*preds)
  MergeSpec.new(preds)
end

.nilable(pred) ⇒ Spec

Returns a spec that accepts nil and values satisfying pred.

Parameters:

  • pred

Returns:

  • (Spec)

    a spec that accepts nil and values satisfying pred



611
612
613
# File 'lib/speculation.rb', line 611

def self.nilable(pred)
  NilableSpec.new(pred)
end

.one_or_more(pred) ⇒ Hash

an array of matches

Parameters:

  • pred

Returns:

  • (Hash)

    regex op that matches one or more values matching pred. Produces



493
494
495
# File 'lib/speculation.rb', line 493

def self.one_or_more(pred)
  pcat(:predicates => [pred, rep(pred, pred, [], true)], :return_value => [])
end

.or(key_preds) ⇒ Spec

Returns a destructuring spec that returns a two element array containing the key of the first matching pred and the corresponding value. Thus the ‘key’ and ‘val’ functions can be used to refer generically to the components of the tagged return.

Examples:

S.or(even: -> (n) { n.even? }, small: -> (n) { n < 42 })

Parameters:

  • key_preds (Hash)

    Takes key+pred hash

Returns:

  • (Spec)

    a destructuring spec that returns a two element array containing the key of the first matching pred and the corresponding value. Thus the ‘key’ and ‘val’ functions can be used to refer generically to the components of the tagged return.



383
384
385
# File 'lib/speculation.rb', line 383

def self.or(key_preds)
  OrSpec.new(key_preds)
end

.or_keys(*ks) ⇒ Object

See Also:



368
369
370
# File 'lib/speculation.rb', line 368

def self.or_keys(*ks)
  [ns(:or), *ks]
end

.regex?(x) ⇒ Hash, false

Returns x if x is a (Speculation) regex op, else logical false.

Parameters:

  • x (Hash, Object)

Returns:

  • (Hash, false)

    x if x is a (Speculation) regex op, else logical false



116
117
118
# File 'lib/speculation.rb', line 116

def self.regex?(x)
  Utils.hash?(x) && x[ns(:op)] && x
end

.registryHash

Returns the registry hash.

Returns:

  • (Hash)

    the registry hash

See Also:



290
291
292
# File 'lib/speculation.rb', line 290

def self.registry
  @registry_ref.value
end

.spec(pred, gen: nil) ⇒ Spec

NOTE: it is not generally necessary to wrap predicates in spec when using S.def etc., only to attach a unique generator.

Optionally takes :gen generator function, which must be a proc of one arg (Rantly instance) that generates a valid value.

Parameters:

  • pred (Proc, Method, Set, Class, Regexp, Hash)

    Takes a single predicate. A predicate can be one of:

    • Proc, e.g. ‘-> (x) { x.even? }`, will be called with the given value

    • Method, e.g. ‘Foo.method(:bar?)`, will be called with the given value

    • Set, e.g. ‘Set[1, 2]`, will be tested whether it includes the given value

    • Class/Module, e.g. String, will be tested for case equality (is_a?) with the given value

    • Regexp, e.g. /foo/, will be tested using ‘===` with given value

    Can also be passed the result of one of the regex ops - cat, alt, zero_or_more, one_or_more, zero_or_one, in which case it will return a regex-conforming spec, useful when nesting an independent regex.

  • gen (Proc) (defaults to: nil)

    generator function, which must be a proc of one arg (Rantly instance) that generates a valid value.

Returns:

  • (Spec)


323
324
325
326
327
328
329
# File 'lib/speculation.rb', line 323

def self.spec(pred, gen: nil)
  if pred
    spec_impl(pred, false).tap do |spec|
      spec.gen = gen if gen
    end
  end
end

.spec?(x) ⇒ Spec, false

Returns x if x is a spec, else false.

Parameters:

  • x (Spec, Object)

Returns:

  • (Spec, false)

    x if x is a spec, else false



110
111
112
# File 'lib/speculation.rb', line 110

def self.spec?(x)
  x if x.is_a?(SpecImpl)
end

.time_in(time_range) ⇒ Object

Returns Spec that validates times in the given range.

Parameters:

  • time_range (Range<Time>)

Returns:

  • Spec that validates times in the given range



96
97
98
99
# File 'lib/speculation.rb', line 96

def self.time_in(time_range)
  spec(self.and(Time, ->(x) { time_range.cover?(x) }),
       :gen => ->(_) { rand(time_range) })
end

.tuple(*preds) ⇒ Spec

Returns a spec for a tuple, an array where each element conforms to the corresponding pred. Each element will be referred to in paths using its ordinal.

Parameters:

  • preds (Array)

    one or more preds

Returns:

  • (Spec)

    a spec for a tuple, an array where each element conforms to the corresponding pred. Each element will be referred to in paths using its ordinal.



567
568
569
# File 'lib/speculation.rb', line 567

def self.tuple(*preds)
  TupleSpec.new(preds)
end

.valid?(spec, x) ⇒ Boolean

Returns true when x is valid for spec.

Parameters:

  • spec
  • x

Returns:

  • (Boolean)

    true when x is valid for spec.



602
603
604
605
606
607
# File 'lib/speculation.rb', line 602

def self.valid?(spec, x)
  spec = Identifier(spec)
  spec = specize(spec)

  !invalid?(spec.conform(x))
end

.with_gen(spec, gen) ⇒ Spec

Takes a spec and a one-arg generator function and returns a version of the spec that uses that generator

Parameters:

  • spec (Spec)
  • gen (Proc)

    generator proc that receives a Rantly instance

Returns:

  • (Spec)


138
139
140
141
142
143
144
# File 'lib/speculation.rb', line 138

def self.with_gen(spec, gen)
  if regex?(spec)
    spec.merge(ns(:gfn) => gen)
  else
    specize(spec).tap { |s| s.gen = gen }
  end
end

.zero_or_more(pred) ⇒ Hash

Returns regex op that matches zero or more values matching pred. Produces an array of matches iff there is at least one match.

Parameters:

  • pred

Returns:

  • (Hash)

    regex op that matches zero or more values matching pred. Produces an array of matches iff there is at least one match



486
487
488
# File 'lib/speculation.rb', line 486

def self.zero_or_more(pred)
  rep(pred, pred, [], false)
end

.zero_or_one(pred) ⇒ Hash

single value (not a collection) if matched.

Parameters:

  • pred

Returns:

  • (Hash)

    regex op that matches zero or one value matching pred. Produces a



500
501
502
# File 'lib/speculation.rb', line 500

def self.zero_or_one(pred)
  _alt([pred, accept(ns(:nil))], nil)
end