Skip to content

Instantly share code, notes, and snippets.

@JoshCheek
Created November 11, 2015 00:43
Show Gist options
  • Save JoshCheek/865c601e205478ae2736 to your computer and use it in GitHub Desktop.
Save JoshCheek/865c601e205478ae2736 to your computer and use it in GitHub Desktop.
Potential interface assertions
class Mongoid
def some_mongoid_method
end
end
module SomeInterface
def common_method1(arg1)
end
def common_method2
end
end
class MatchesInterface < Mongoid
def common_method1(arg1)
end
def common_method2
end
end
class ExtraMethods < Mongoid
def common_method1(arg1)
end
def common_method2
end
def extra1
end
def extra2(a,b:1)
end
end
class MissingMethod < Mongoid
def common_method1(arg1)
end
end
class MisspelledMethod < Mongoid
def common_method1(arg1)
end
def common_method2_misspelled
end
end
class ExtraArg < Mongoid
def common_method1(arg1, arg2)
end
def common_method2
end
end
class MissingArg < Mongoid
def common_method1
end
def common_method2
end
end
class MisspelledArg < Mongoid
def common_method1(not_arg1)
end
def common_method2
end
end
module Interface
def self.deviations(interface, implementation, ignore:[])
# find all signatures
instance = implementation.allocate
actuals = instance.methods.map { |name| [name, instance.method(name).parameters] }.to_h
expecteds = interface.instance_methods.map { |name| [name, interface.instance_method(name).parameters] }.to_h
# which methods to check
ignore_names = Array(ignore).flat_map(&:instance_methods)
method_names = (expecteds.keys|actuals.keys) - ignore_names
# record deviations
method_names.each_with_object missing:[], extra:[], incongruent:[] do |name, results|
if expecteds.key?(name) && actuals.key?(name) && expecteds[name] == actuals[name]
# no op (they match)
elsif expecteds.key?(name) && actuals.key?(name)
results[:incongruent] << {name: name, expected: expecteds[name], actual: actuals[name]}
elsif expecteds.key?(name)
results[:missing] << {name: name, parameters: expecteds[name], owner: interface.instance_method(name).owner}
else
results[:extra] << {name: name, parameters: actuals[name], owner: instance.method(name).owner}
end
end
end
end
require 'rspec/autorun'
RSpec::Matchers.define :implement do |interface, ignore:nil|
# {missing: [{name: :common_method2, parameters: [], owner: SomeInterface}],
# extra: [{name: :some_mongoid_method, parameters: [], owner: Mongoid}],
# incongruent: [{name: :common_method1, expected: [[:req, :arg1]], actual: [[:req, :arg1], [:req, :arg2]]}]}
deviations = :uninitialized
match do |implementation|
deviations = Interface.deviations(interface, implementation, ignore: ignore)
deviations.all? { |_type, methods| methods.empty? }
end
failure_message do |implementation|
[ *deviations[:missing].map { |name:, parameters:, owner:| "missing: #{owner}##{name}(#{inspect_params parameters})" },
*deviations[:extra].map { |name:, parameters:, owner:| "extra: #{owner}##{name}(#{inspect_params parameters})" },
*deviations[:incongruent].map { |name:, expected:, actual:| "incongruent params: expected #{name}(#{inspect_params expected}), actual #{name}(#{inspect_params actual})"},
].join("\n")
end
def inspect_params(params)
params.map { |type, name|
case type
when :req then name
when :opt then "#{name}=??"
when :rest then "*#{name}"
when :key then "#{name}:??"
when :keyreq then "#{name}:"
when :keyrest then "**#{name}"
when :block then "&#{name}"
else raise "Handle this arg type: #{type.inspect}"
end
}.join(', ')
end
end
RSpec.describe 'Implementations of SomeInterface' do
specify 'A match will match if we ignore inherited methods' do
expect(MatchesInterface).to implement SomeInterface, ignore: Mongoid
end
specify 'A match will fail due to inherited methods if we don\'t ignore them' do
expect(MatchesInterface).to implement SomeInterface, ignore: Object
end
specify 'Class will fail if it has extra methods' do
expect(ExtraMethods).to implement SomeInterface, ignore: Mongoid
end
specify 'Class will fail if it is missing methods' do
expect(MissingMethod).to implement SomeInterface, ignore: Mongoid
end
specify 'Misspelled methods show up in both extra and missing' do
expect(MisspelledMethod).to implement SomeInterface, ignore: Mongoid
end
describe 'Fails when args don\'t match' do
example('extra arg') { expect(ExtraArg ).to implement SomeInterface, ignore: Mongoid }
example('missing arg') { expect(MissingArg ).to implement SomeInterface, ignore: Mongoid }
example('misspelled arg') { expect(MisspelledArg).to implement SomeInterface, ignore: Mongoid }
end
example 'A little of everything' do
interface = Module.new do
def self.to_s() "Interface" ; end
def match ; end
def missing1 ; end
def missing2 ; end
def misspelled ; end
def mismatch(a,b=1,*c,d,e:,f:1,**g,&h) ; end
end
implementation = Class.new do
def self.to_s() "Implementation" ; end
def match ; end
def misspeled ; end
def extra1 ; end
def extra2 ; end
def mismatch ; end
end
expect(implementation).to implement interface, ignore: Object
end
end
# >> .FFFFFFFF
# >>
# >> Failures:
# >>
# >> 1) Implementations of SomeInterface A match will fail due to inherited methods if we don't ignore them
# >> Failure/Error: expect(MatchesInterface).to implement SomeInterface, ignore: Object
# >> extra: Mongoid#some_mongoid_method()
# >> # /var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20151110-59968-1lryaky/program.rb:136:in `block (2 levels) in <main>'
# >>
# >> 2) Implementations of SomeInterface Class will fail if it has extra methods
# >> Failure/Error: expect(ExtraMethods).to implement SomeInterface, ignore: Mongoid
# >> extra: ExtraMethods#extra1()
# >> extra: ExtraMethods#extra2(a, b:??)
# >> # /var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20151110-59968-1lryaky/program.rb:140:in `block (2 levels) in <main>'
# >>
# >> 3) Implementations of SomeInterface Class will fail if it is missing methods
# >> Failure/Error: expect(MissingMethod).to implement SomeInterface, ignore: Mongoid
# >> missing: SomeInterface#common_method2()
# >> # /var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20151110-59968-1lryaky/program.rb:144:in `block (2 levels) in <main>'
# >>
# >> 4) Implementations of SomeInterface Misspelled methods show up in both extra and missing
# >> Failure/Error: expect(MisspelledMethod).to implement SomeInterface, ignore: Mongoid
# >> missing: SomeInterface#common_method2()
# >> extra: MisspelledMethod#common_method2_misspelled()
# >> # /var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20151110-59968-1lryaky/program.rb:148:in `block (2 levels) in <main>'
# >>
# >> 5) Implementations of SomeInterface A little of everything
# >> Failure/Error: expect(implementation).to implement interface, ignore: Object
# >> missing: Interface#missing1()
# >> missing: Interface#missing2()
# >> missing: Interface#misspelled()
# >> extra: Implementation#misspeled()
# >> extra: Implementation#extra1()
# >> extra: Implementation#extra2()
# >> incongruent params: expected mismatch(a, b=??, *c, d, e:, f:??, **g, &h), actual mismatch()
# >> # /var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20151110-59968-1lryaky/program.rb:174:in `block (2 levels) in <main>'
# >>
# >> 6) Implementations of SomeInterface Fails when args don't match extra arg
# >> Failure/Error: example('extra arg') { expect(ExtraArg ).to implement SomeInterface, ignore: Mongoid }
# >> incongruent params: expected common_method1(arg1), actual common_method1(arg1, arg2)
# >> # /var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20151110-59968-1lryaky/program.rb:152:in `block (3 levels) in <main>'
# >>
# >> 7) Implementations of SomeInterface Fails when args don't match missing arg
# >> Failure/Error: example('missing arg') { expect(MissingArg ).to implement SomeInterface, ignore: Mongoid }
# >> incongruent params: expected common_method1(arg1), actual common_method1()
# >> # /var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20151110-59968-1lryaky/program.rb:153:in `block (3 levels) in <main>'
# >>
# >> 8) Implementations of SomeInterface Fails when args don't match misspelled arg
# >> Failure/Error: example('misspelled arg') { expect(MisspelledArg).to implement SomeInterface, ignore: Mongoid }
# >> incongruent params: expected common_method1(arg1), actual common_method1(not_arg1)
# >> # /var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20151110-59968-1lryaky/program.rb:154:in `block (3 levels) in <main>'
# >>
# >> Finished in 0.01063 seconds (files took 0.0874 seconds to load)
# >> 9 examples, 8 failures
# >>
# >> Failed examples:
# >>
# >> rspec /var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20151110-59968-1lryaky/program.rb[1:2] # Implementations of SomeInterface A match will fail due to inherited methods if we don't ignore them
# >> rspec /var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20151110-59968-1lryaky/program.rb[1:3] # Implementations of SomeInterface Class will fail if it has extra methods
# >> rspec /var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20151110-59968-1lryaky/program.rb[1:4] # Implementations of SomeInterface Class will fail if it is missing methods
# >> rspec /var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20151110-59968-1lryaky/program.rb[1:5] # Implementations of SomeInterface Misspelled methods show up in both extra and missing
# >> rspec /var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20151110-59968-1lryaky/program.rb[1:7] # Implementations of SomeInterface A little of everything
# >> rspec /var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20151110-59968-1lryaky/program.rb[1:6:1] # Implementations of SomeInterface Fails when args don't match extra arg
# >> rspec /var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20151110-59968-1lryaky/program.rb[1:6:2] # Implementations of SomeInterface Fails when args don't match missing arg
# >> rspec /var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20151110-59968-1lryaky/program.rb[1:6:3] # Implementations of SomeInterface Fails when args don't match misspelled arg
# >>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment