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



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

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.



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

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.

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.



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

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.



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

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

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



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

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>



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

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.



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

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.



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

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:



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

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.



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

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



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

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