egonSchiele / contracts.ruby

Contracts for Ruby.
http://egonschiele.github.com/contracts.ruby
BSD 2-Clause "Simplified" License
1.44k stars 82 forks source link

Contracts errors on self.methods in unnamed modules #46

Open mcandre opened 10 years ago

mcandre commented 10 years ago

Contracts reports a spurious contract violation error, then allows normal execution flow anyway.

Source

https://github.com/mcandre/mcandre/blob/9f587d85754d02f2f10d24062d924d4e427144a9/ruby/beer.rb

Trace

$ ./beer.rb 
99 bottles of beer on the wall, 99 bottles of beer.
Take one down, pass it around, 98 bottles of beer on the wall.

98 bottles of beer on the wall, 98 bottles of beer.
Take one down, pass it around, 97 bottles of beer on the wall.

97 bottles of beer on the wall, 97 bottles of beer.
Take one down, pass it around, 96 bottles of beer on the wall.

96 bottles of beer on the wall, 96 bottles of beer.
Take one down, pass it around, 95 bottles of beer on the wall.

...

3 bottles of beer on the wall, 3 bottles of beer.
Take one down, pass it around, 2 bottles of beer on the wall.

2 bottles of beer on the wall, 2 bottle of beer.Take one down, pass it around, 1 bottle of beer on the wall.

1 bottle of beer on the wall, 1 bottle of beer.
Go to the store and buy some more, 99 bottles of beer on the wall.
/home/apenneb/.rvm/gems/ruby-2.0.0-p481/gems/contracts-0.4/lib/contracts.rb:132:in `failure_callback': Contract violation for return value: (ContractError)
    Expected: String,
    Actual: 99
    Value guarded in: Object::main
    With Contract: Num => String
    At: ./beer.rb:20 
    from /home/apenneb/.rvm/gems/ruby-2.0.0-p481/gems/contracts-0.4/lib/contracts.rb:236:in `call_with'
    from /home/apenneb/.rvm/gems/ruby-2.0.0-p481/gems/contracts-0.4/lib/decorators.rb:157:in `main'
    from ./beer.rb:26:in `<main>'

Notes

Seems to me that contracts is mistakenly applying the contract to #main rather than #beer. If I tweak #main to collect the lines before printing:

...

def main
  99.downto(1).collect { |i| beer i }.each { |line| puts line }
end

...

Then contracts complains:

$ ./beer.rb
3 bottles of beer on the wall, 3 bottles of beer.
Take one down, pass it around, 2 bottles of beer on the wall.

2 bottles of beer on the wall, 2 bottle of beer.Take one down, pass it around, 1 bottle of beer on the wall.

1 bottle of beer on the wall, 1 bottle of beer.
Go to the store and buy some more, 99 bottles of beer on the wall.
/home/apenneb/.rvm/gems/ruby-2.0.0-p481/gems/contracts-0.4/lib/contracts.rb:132:in `failure_callback': Contract violation for return value: (ContractError)
    Expected: String,
    Actual: ["99 bottles of beer on the wall, 99 bottles of beer.\nTake one down, pass it around, 98 bottles of beer on the wall.\n\n", "98 bottles of beer on the wall, 98 bottles of beer.\nTake one down, pass it around, 97 bottles of beer on the wall.\n\n", "97 bottles of beer on the wall, 97 bottles of beer.\nTake one down, pass it around, 96 bottles of beer on the wall.\n\n", "96 bottles of beer on the wall, 96 bottles of beer.\nTake one down, pass it around, 95 bottles of beer on the wall.\n\n", "95 bottles of beer on the wall, 95 bottles of beer.\nTake one down, pass it around, 94 bottles of beer on the wall.\n\n", ...

System

mcandre commented 10 years ago

Update: The problem appears to be specific to unnamed modules with self. methods. If I give the module a specific name, e.g.:

#!/usr/bin/env ruby

require 'contracts'
include Contracts

module Beer
  Contract Num => String
  def self.beer(i)
    if i >= 3
      "#{i} bottles of beer on the wall, #{i} bottles of beer.\n" +
        "Take one down, pass it around, #{i - 1} bottles of beer on the wall.\n\n"
    elsif i > 1
      "2 bottles of beer on the wall, 2 bottle of beer." +
        "Take one down, pass it around, 1 bottle of beer on the wall.\n\n"
    else
      "1 bottle of beer on the wall, 1 bottle of beer.\n" +
        "Go to the store and buy some more, 99 bottles of beer on the wall."
    end
  end
end

def main
  99.downto(1).collect { |i| Beer.beer i }.each { |line| puts line }
end

main if $PROGRAM_NAME == __FILE__

Then the problem is mitigated.

egonSchiele commented 10 years ago

Thank you for finding these corner cases!

egonSchiele commented 9 years ago

top-level inclusion of Contracts will be deprecated soon: https://github.com/egonSchiele/contracts.ruby/issues/81

waterlink commented 9 years ago

Happens only on global scope and it will be gone once we forbid global inclusion of Contracts.