class FlexMock::SignatureValidator

Validate that the call matches a given signature

The validator created by {#initialize} matches any method call

Attributes

optional_arguments[R]

The number of optional arguments

optional_keyword_arguments[R]

The names of optional keyword arguments @return [Set<Symbol>]

required_arguments[R]

The number of required arguments

required_keyword_arguments[R]

The names of required keyword arguments @return [Set<Symbol>]

Public Class Methods

from_instance_method(exp, instance_method) click to toggle source

Create a validator that represents the signature of an existing method

# File lib/flexmock/validators.rb, line 291
def self.from_instance_method(exp, instance_method)
  required_arguments, optional_arguments, splat = 0, 0, false
  required_keyword_arguments, optional_keyword_arguments, keyword_splat = Set.new, Set.new, false
  instance_method.parameters.each do |type, name|
    case type
    when :req then required_arguments += 1
    when :opt then optional_arguments += 1
    when :rest then splat = true
    when :keyreq then required_keyword_arguments << name
    when :key then optional_keyword_arguments << name
    when :keyrest then keyword_splat = true
    when :block
    else raise ArgumentError, "cannot interpret parameter type #{type}"
    end
  end
  new(exp,
      required_arguments: required_arguments,
      optional_arguments: optional_arguments,
      splat: splat,
      required_keyword_arguments: required_keyword_arguments,
      optional_keyword_arguments: optional_keyword_arguments,
      keyword_splat: keyword_splat)
end
new( expectation, required_arguments: 0, optional_arguments: 0, splat: true, required_keyword_arguments: [], optional_keyword_arguments: [], keyword_splat: true) click to toggle source
# File lib/flexmock/validators.rb, line 177
def initialize(
    expectation,
    required_arguments: 0,
    optional_arguments: 0,
    splat: true,
    required_keyword_arguments: [],
    optional_keyword_arguments: [],
    keyword_splat: true)
  @exp = expectation
  @required_arguments = required_arguments
  @optional_arguments = optional_arguments
  @required_keyword_arguments = required_keyword_arguments.to_set
  @optional_keyword_arguments = optional_keyword_arguments.to_set
  @splat = splat
  @keyword_splat = keyword_splat
end

Public Instance Methods

describe() click to toggle source
# File lib/flexmock/validators.rb, line 201
def describe
  ".with_signature(
      required_arguments: #{self.required_arguments},
      optional_arguments: #{self.optional_arguments},
      required_keyword_arguments: #{self.required_keyword_arguments.to_a},
      optional_keyword_arguments: #{self.optional_keyword_arguments.to_a},
      splat: #{self.splat?},
      keyword_splat: #{self.keyword_splat?})"
end
expects_keyword_arguments?() click to toggle source

Whether this method may have keyword arguments

# File lib/flexmock/validators.rb, line 168
def expects_keyword_arguments?
  keyword_splat? || !required_keyword_arguments.empty? || !optional_keyword_arguments.empty?
end
keyword_splat?() click to toggle source

Whether there is a splat for keyword arguments (double-star)

# File lib/flexmock/validators.rb, line 163
def keyword_splat?
  @keyword_splat
end
null?() click to toggle source

Whether this tests anything

It will return if this validator would validate any set of arguments

# File lib/flexmock/validators.rb, line 197
def null?
  splat? && keyword_splat?
end
requires_keyword_arguments?() click to toggle source

Whether this method may have keyword arguments

# File lib/flexmock/validators.rb, line 173
def requires_keyword_arguments?
  !required_keyword_arguments.empty?
end
splat?() click to toggle source

Whether there is a positional argument splat

# File lib/flexmock/validators.rb, line 153
def splat?
  @splat
end
validate(args) click to toggle source

Validates whether the given arguments match the expected signature

@param [Array] args @raise ValidationFailed

# File lib/flexmock/validators.rb, line 215
def validate(args)
  args = args.dup
  kw_args = Hash.new

  last_is_proc = false
  begin
    if args.last.kind_of?(Proc)
      args.pop
      last_is_proc = true
    end
  rescue NoMethodError
  end

  last_is_kw_hash = false
  if expects_keyword_arguments?
    last_is_kw_hash =
      begin
        args.last.kind_of?(Hash)
      rescue NoMethodError
      end

    if last_is_kw_hash
      kw_args = args.pop
    elsif requires_keyword_arguments?
      raise ValidationFailed, "#{@exp} expects keyword arguments but none were provided"
    end
  end

  # There is currently no way to disambiguate "given a block" from "given a
  # proc as last argument" ... give some leeway in this case
  positional_count = args.size

  if required_arguments > positional_count
    if requires_keyword_arguments?
      raise ValidationFailed, "#{@exp} expects at least #{required_arguments} positional arguments but got only #{positional_count}"
    end

    if (required_arguments - positional_count) == 1 && (last_is_kw_hash || last_is_proc)
      if last_is_kw_hash
        last_is_kw_hash = false
        kw_args = Hash.new
      else
        last_is_proc = false
      end
      positional_count += 1
    elsif (required_arguments - positional_count) == 2 && (last_is_kw_hash && last_is_proc)
      last_is_kw_hash = false
      kw_args = Hash.new
      last_is_proc = false
      positional_count += 2
    else
      raise ValidationFailed, "#{@exp} expects at least #{required_arguments} positional arguments but got only #{positional_count}"
    end
  end

  if !splat? && (required_arguments + optional_arguments) < positional_count
    if !last_is_proc || (required_arguments + optional_arguments) < positional_count - 1
      raise ValidationFailed, "#{@exp} expects at most #{required_arguments + optional_arguments} positional arguments but got #{positional_count}"
    end
  end

  missing_keyword_arguments = required_keyword_arguments.
    find_all { |k| !kw_args.has_key?(k) }
  if !missing_keyword_arguments.empty?
    raise ValidationFailed, "#{@exp} missing required keyword arguments #{missing_keyword_arguments.map(&:to_s).sort.join(", ")}"
  end
  if !keyword_splat?
    kw_args.each_key do |k|
      if !optional_keyword_arguments.include?(k) && !required_keyword_arguments.include?(k)
        raise ValidationFailed, "#{@exp} given unexpected keyword argument #{k}"
      end
    end
  end
end