Module: Speculation::Test

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

Class Attribute Summary collapse

Class Method Summary collapse

Methods included from NamespacedSymbols

namespace, namespaced_name, ns, symbol

Class Attribute Details

.instrument_enabledObject

if false, instrumented methods call straight through


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

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)

175
176
177
178
179
180
181
182
183
# File 'lib/speculation/test.rb', line 175

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

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

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<Hash>)

    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 :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


158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/speculation/test.rb', line 158

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

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

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

  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:


118
119
120
121
# File 'lib/speculation/test.rb', line 118

def self.check_method(method, spec, opts = {})
  validate_check_opts(opts)
  check1(S.MethodIdentifier(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.


125
126
127
128
129
130
131
132
133
134
# File 'lib/speculation/test.rb', line 125

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


212
213
214
# File 'lib/speculation/test.rb', line 212

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.


84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/speculation/test.rb', line 84

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

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

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

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

Parameters:

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

Returns:

  • (Array<Method>)

40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/speculation/test.rb', line 40

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:


194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/speculation/test.rb', line 194

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


102
103
104
105
106
107
108
109
110
# File 'lib/speculation/test.rb', line 102

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

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

.with_instrument_disabledObject

Disables instrument's checking of calls within a block


29
30
31
32
33
34
# File 'lib/speculation/test.rb', line 29

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