Module: Speculation::Test

Extended by:
NamespacedSymbols
Defined in:
lib/speculation/test.rb

Class Attribute Summary collapse

Class Method Summary collapse

Methods included from NamespacedSymbols

name, namespace, ns, symbol

Class Attribute Details

.instrument_enabledObject

if false, instrumented methods call straight through



21
22
23
# File 'lib/speculation/test.rb', line 21

def instrument_enabled
  @instrument_enabled
end

Class Method Details

.abbrev_result(x) ⇒ Hash

Given a check result, returns an abbreviated version suitable for summary use.

Parameters:

  • x (Hash)

Returns:

  • (Hash)


171
172
173
174
175
176
177
178
179
# File 'lib/speculation/test.rb', line 171

def self.abbrev_result(x)
  if x[:failure]
    x.reject { |k, _| k == ns(:ret) }.
      merge(:spec    => x[:spec].inspect,
            :failure => unwrap_failure(x[:failure]))
  else
    x.reject { |k, _| [:spec, ns(:ret)].include?(k) }
  end
end

.check(method_or_methods = nil, opts = {}) ⇒ Array<Identifier>

Run generative tests for spec conformance on method_or_methods. If method_or_methods is not specified, check all checkable methods.

Parameters:

  • method_or_methods (Array<Method>, Method) (defaults to: nil)
  • opts (Hash) (defaults to: {})

Options Hash (opts):

  • :num_tests (Integer) — default: 1000

    number of times to generatively test each method

  • :gen (Hash)

    map from spec names to generator overrides. Generator overrides are passed to Speculation.gen when generating method args.

Returns:

  • (Array<Identifier>)

    an array of check result hashes with the following keys:

    • :spec the spec tested

    • :method optional method tested

    • :failure optional test failure

    • :result optional boolean as to whether all generative tests passed

    • :num_tests optional number of generative tests ran

    :failure is a hash that will contain a :“Speculation/failure” key with possible values:

    • :check_failed at least one checked return did not conform

    • :no_args_spec no :args spec provided

    • :no_fspec no fspec provided

    • :no_gen unable to generate :args

    • :instrument invalid args detected by instrument



154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/speculation/test.rb', line 154

def self.check(method_or_methods = nil, opts = {})
  method_or_methods ||= checkable_methods

  checkable = Set(checkable_methods(opts))
  checkable.map!(&S.method(:Identifier))

  methods = Set(method_or_methods)
  methods.map!(&S.method(:Identifier))

  pmap(methods.intersection(checkable)) { |ident|
    check1(ident, S.get_spec(ident), opts)
  }
end

.check_method(method, spec, opts = {}) ⇒ Hash

Runs generative tests for method using spec and opts.

Parameters:

  • method (Method)
  • spec (Spec)
  • opts (Hash) (defaults to: {})

Returns:

  • (Hash)

See Also:



114
115
116
117
# File 'lib/speculation/test.rb', line 114

def self.check_method(method, spec, opts = {})
  validate_check_opts(opts)
  check1(S.Identifier(method), spec, opts)
end

.checkable_methods(opts = {}) ⇒ Array<Method>

Returns the array of methods that can be checked.

Parameters:

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

    an opts hash as per ‘check`

Returns:

  • (Array<Method>)

    the array of methods that can be checked.



121
122
123
124
125
126
127
128
129
130
# File 'lib/speculation/test.rb', line 121

def self.checkable_methods(opts = {})
  validate_check_opts(opts)

  S.
    registry.
    keys.
    select { |k| fn_spec_name?(k) && !k.instance_method? }.
    concat(Hash(opts[:spec]).keys).
    map(&method(:Method))
end

.enumerate_methods(*modules) ⇒ Array<Method>

Returns an array of public and protected singleton methods belonging to modules.

Parameters:

  • modules (Module, Class)

Returns:

  • (Array<Method>)

    an array of public and protected singleton methods belonging to modules



208
209
210
# File 'lib/speculation/test.rb', line 208

def self.enumerate_methods(*modules)
  modules.flat_map { |mod| mod.methods(false).map(&mod.method(:method)) } # method
end

.instrument(method_or_methods = instrumentable_methods, opts = {}) ⇒ Array<Method>

Returns a collection of methods instrumented.

Parameters:

  • method_or_methods (Method, Array<Method>) (defaults to: instrumentable_methods)

    Instruments the methods named by method-or-methods, a method or collection of methods, or all instrumentable methods if method_or_methods is not specified. If a method has an :args fn-spec, replaces the method with a method that checks arg conformance (throwing an exception on failure) before delegating to the original method.

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

    opts hash can be used to override registered specs, and/or to replace method implementations entirely. Opts for methods not included in method-or-methods are ignored. This facilitates sharing a common options hash across many different calls to instrument

Options Hash (opts):

  • :spec (Hash)

    a map from methods to override specs. :spec overrides registered method specs with specs you provide. Use :spec overrides to provide specs for libraries that do not have them, or to constrain your own use of a fn to a subset of its spec’ed contract. :spec can be used in combination with :stub or :replace.

  • :stub (Set, Array)

    a set of methods to be replaced by stubs. :stub replaces a fn with a stub that checks :args, then uses the :ret spec to generate a return value.

  • :gen (Hash{Symbol => Proc})

    a map from spec names to generator overrides. :gen overrides are used only for :stub generation.

  • :replace (Hash{Method => Proc})

    a map from methods to replacement procs. :replace replaces a method with a method that checks args conformance, then invokes the method/proc you provide, enabling arbitrary stubbing and mocking.

Returns:

  • (Array<Method>)

    a collection of methods instrumented.



80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/speculation/test.rb', line 80

def self.instrument(method_or_methods = instrumentable_methods, opts = {})
  if opts[:gen]
    gens = opts[:gen].reduce({}) { |h, (k, v)| h.merge(S.Identifier(k) => v) }
    opts = opts.merge(:gen => gens)
  end

  Array(method_or_methods).
    map { |method| S.Identifier(method) }.
    uniq.
    map { |ident| instrument1(ident, opts) }.
    compact.
    map(&method(:Method))
end

.instrumentable_methods(opts = {}) ⇒ Array<Identifier>

Given an opts hash as per instrument, returns the set of methods that can be instrumented.

Parameters:

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

Returns:

  • (Array<Identifier>)


36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/speculation/test.rb', line 36

def self.instrumentable_methods(opts = {})
  if opts[:gen]
    unless opts[:gen].keys.all? { |k| k.is_a?(Method) || k.is_a?(Symbol) }
      raise ArgumentError, "instrument :gen expects Method or Symbol keys"
    end
  end

  S.registry.keys.select(&method(:fn_spec_name?)).to_set.tap { |set|
    set.merge(opts[:spec].keys)    if opts[:spec]
    set.merge(opts[:stub])         if opts[:stub]
    set.merge(opts[:replace].keys) if opts[:replace]
  }.map(&method(:Method))
end

.summarize_results(check_results) {|Hash| ... } ⇒ Hash

Given a collection of check_results, e.g. from ‘check`, pretty prints the summary_result (default abbrev_result) of each.

Parameters:

  • check_results (Array)

    a collection of check_results

Yields:

  • (Hash)

Returns:

  • (Hash)

    a hash with :total, the total number of results, plus a key with a count for each different :type of result.

See Also:



190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/speculation/test.rb', line 190

def self.summarize_results(check_results, &summary_result)
  summary_result ||= method(:abbrev_result)

  check_results.reduce(:total => 0) { |summary, result|
    pp summary_result.call(result)

    result_key = result_type(result)

    summary.merge(
      :total     => summary[:total].next,
      result_key => summary.fetch(result_key, 0).next
    )
  }
end

.unstrument(method_or_methods = nil) ⇒ Array<Method>

Undoes instrument on the method_or_methods, specified as in instrument. With no args, unstruments all instrumented methods.

Parameters:

  • method_or_methods (Method, Array<Method>) (defaults to: nil)

Returns:

  • (Array<Method>)

    a collection of methods unstrumented



98
99
100
101
102
103
104
105
106
# File 'lib/speculation/test.rb', line 98

def self.unstrument(method_or_methods = nil)
  method_or_methods ||= @instrumented_methods.value.keys

  Array(method_or_methods).
    map { |method| S.Identifier(method) }.
    map { |ident| unstrument1(ident) }.
    compact.
    map(&method(:Method))
end

.with_instrument_disabledObject

Disables instrument’s checking of calls within a block



25
26
27
28
29
30
# File 'lib/speculation/test.rb', line 25

def self.with_instrument_disabled
  instrument_enabled.value = false
  yield
ensure
  instrument_enabled.value = true
end