Contracts errors on self.methods in unnamed modules
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
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.
Thank you for finding these corner cases!
top-level inclusion of Contracts will be deprecated soon: https://github.com/egonSchiele/contracts.ruby/issues/81
Happens only on global scope and it will be gone once we forbid global inclusion of Contracts.