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.


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.

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.


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.


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>


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>


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.

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.


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.


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.

Yields:

  • (Hash)

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.


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