contracts.ruby icon indicating copy to clipboard operation
contracts.ruby copied to clipboard

Contracts errors on self.methods in unnamed modules

Open mcandre opened this issue 11 years ago • 4 comments

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

  • contracts 0.4
  • Ruby 2.0
  • Ubuntu 14.04

mcandre avatar Aug 12 '14 15:08 mcandre

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.

mcandre avatar Aug 12 '14 15:08 mcandre

Thank you for finding these corner cases!

egonSchiele avatar Aug 12 '14 16:08 egonSchiele

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

egonSchiele avatar Mar 08 '15 21:03 egonSchiele

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

waterlink avatar Sep 04 '15 21:09 waterlink