Created
November 11, 2015 00:43
-
-
Save JoshCheek/865c601e205478ae2736 to your computer and use it in GitHub Desktop.
Potential interface assertions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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