require 'test/unit' $LOAD_PATH << "../lib" require 'contract' module SignatureHelpers def object(type = Object, *args, &block) Class.new(type, &block).new(*args) end end class TC_Integration_Signature < Test::Unit::TestCase include SignatureHelpers def setup() Contract.check_signatures = true end def test_incompatible_signature() exception = assert_raise(ArgumentError) do object do signature :to_s, :any end end assert_equal("signature isn't compatible with arity", exception.message) end def test_empty_repeated() exception = assert_raise(ArgumentError) do object do signature :to_s, :repeated => [] end end assert_equal("repeated arguments may not be an empty Array", exception.message) end def test_invalid_specifier() assert_raise(ArgumentError) do object do signature :is_a?, Class.new { undef :=== }.new end end end def test_no_arguments() obj = object do alias :old_to_s :to_s signature :to_s end assert_nothing_raised { obj.to_s } assert_equal(obj.old_to_s, obj.to_s) assert_raise(ArgumentError) { obj.to_s(1) } end def test_return() obj = object do signature :inspect, :return => String def foo() end signature :foo, :return => String end assert_nothing_raised { obj.inspect } assert_raise(StandardError) { obj.foo } end def test_no_block() obj = object do signature :to_s, :block => false end assert_nothing_raised { obj.to_s } assert_raise(ArgumentError) { obj.to_s { 1+1 } } end def test_block() obj = object(Array) do signature :each, :block => true end assert_nothing_raised { obj.each {} } assert_raise(ArgumentError) { obj.each } end def test_any_arguments() obj = object do signature :respond_to?, :any, :any end assert_nothing_raised { obj.respond_to?(:inspect, true) } assert_raise(ArgumentError) { obj.respond_to?() } end def test_allow_trailing() obj = object(Array) do signature :zip, :allow_trailing => true signature :values_at, :any, :allow_trailing => true end assert_nothing_raised { obj.zip } assert_nothing_raised { obj.zip(obj) } assert_nothing_raised { obj.values_at(0) } assert_nothing_raised { obj.values_at(0, 0) } assert_raise(ArgumentError) { obj.values_at } end def test_classes() obj = object(Array) do signature :is_a?, Module signature :join, String signature :at, Integer end assert_nothing_raised { obj.is_a?(Array) } assert_nothing_raised { obj.join(", ") } assert_nothing_raised { obj.at(0) } assert_raise(ArgumentError) { obj.is_a?(5) } assert_raise(ArgumentError) { obj.join(5) } assert_raise(ArgumentError) { obj.at("x") } end def test_callable() obj = object(Array) do alias :x :* signature :*, lambda { |arg| arg >= 0 } signature :x, 0.method(:<=) end assert_nothing_raised { obj * 0 } assert_nothing_raised { obj.x(0) } assert_raise(ArgumentError) { obj * -1 } assert_raise(ArgumentError) { obj.x(-1) } end def test_optional() obj = object do def foo(x, y=5, z=3, *args) [x, y, z, *args] end signature :foo, Numeric, :optional => [Numeric, Numeric], :allow_trailing => true end assert_nothing_raised { obj.foo(5) } assert_equal([5, 5, 3], obj.foo(5)) assert_nothing_raised { obj.foo(5, 4, 3) } assert_nothing_raised { obj.foo(5, 4, 3, 2) } assert_equal([5, 4, 3, 2], obj.foo(5, 4, 3, 2)) assert_raise(ArgumentError) { obj.foo } assert_raise(ArgumentError) { obj.foo(5, "foo") } end def test_repeated() obj = object(Array) do signature :zip, :repeated => [Enumerable] signature :values_at, Numeric, :repeated => Numeric end assert_nothing_raised { obj.zip } assert_nothing_raised { obj.zip(obj) } assert_nothing_raised { obj.values_at(0) } assert_nothing_raised { obj.values_at(0, 0) } assert_raise(ArgumentError) { obj.values_at } end def test_no_adaption() obj = object do signature :respond_to?, Symbol, :no_adaption => true end assert_nothing_raised { obj.respond_to?(:to_s) } assert_raises(ArgumentError) { obj.respond_to?("to_s") } end end class TC_Integration_Checks < Test::Unit::TestCase def test_block() check = Contract::Check.block { |obj| obj > 0 } assert_equal(true, check === 5) assert_equal(false, check === -5) end def test_quack() check = Contract::Check::Quack[:to_str] assert_equal(true, check === "foo") assert_equal(false, check === Object.new) end def test_all() check = Contract::Check::All[Numeric, 3 .. 10] assert_equal(false, check === "x") assert_equal(false, check === 2) assert_equal(true, check === 5) end def test_any() check = Contract::Check::Any[Numeric, Symbol] assert_equal(false, check === "x") assert_equal(true, check === 5) assert_equal(true, check === :x) end def test_none() check = Contract::Check::None[Numeric, Symbol] assert_equal(true, check === "x") assert_equal(false, check === 5) assert_equal(false, check === :x) end def test_aliases() assert_equal(Contract::Check::All, Contract::Check::And) assert_equal(Contract::Check::Any, Contract::Check::Or) assert_equal(Contract::Check::None, Contract::Check::Not) end end class TC_Integration_Fulfills < Test::Unit::TestCase class ListContract < Contract provides :each, :[] provides :size do assert_nothing_raised { @object.size } assert(@object.size >= 0) end end class ListWrapper def initialize(list) @list = list end def each(&block) @list.each(&block) end def size() @list.size end def [](*args) @list[*args] end fulfills ListContract end def setup() Contract.check_fulfills = true end def test_fulfills() assert_nothing_raised { ListWrapper.new([]) } assert_raise(Contract::ContractMismatch) { ListWrapper.new(Object.new) } end end class TC_Integration_Adaption < Test::Unit::TestCase include SignatureHelpers def test_no_from_and_to() assert_raises(ArgumentError) do object { adaption :via => :to_s } end assert_raises(ArgumentError) do cls = Class.new adaption :from => cls, :to => cls, :via => :to_s end end def test_no_via() assert_raises(ArgumentError) do adaption :from => Class.new, :to => Class.new end end def test_via_and_block() assert_raises(ArgumentError) do adaption :from => Class.new, :to => Class.new, :via => :to_s do |obj| obj.to_s end end end def test_block() obj = object do adaption(:to => String) { "string" } end assert_equal("string", Contract.adapt(obj, String)) end def test_callable_if() cls = Class.new(Object) do attr_accessor :value def initialize(value) @value = value end end obj = object do adaption :from => cls, :to => String, :via => :value, :if => lambda { |obj| obj.value.is_a?(String) } end assert_equal("foo", Contract.adapt(cls.new("foo"), String)) obj = cls.new(5) assert_equal(obj, Contract.adapt(obj, String)) end def test_default_adaptions() obj = object do [Symbol, String, Array, Integer].each do |cls| method = cls.name.downcase.intern define_method(method) { |arg| arg } signature(method, cls) end end string_like = object { def to_str() "foo" end } array_like = object { def to_ary() [1, 2] end } assert_equal(:foo, obj.symbol("foo")) assert_equal("foo", obj.string(string_like)) assert_equal([1, 2], obj.array(array_like)) assert_equal(5, obj.integer(5.0)) end end class TC_Integration_MethodSignatureMixin < Test::Unit::TestCase include SignatureHelpers def test_origin() assert_equal(String, "foo".method(:+).origin) assert_equal(Array, Array.instance_method(:[]).origin) end def test_name() assert_equal(:+, "foo".method(:+).name) assert_equal(:[], Array.instance_method(:[]).name) end def test_has_signature?() assert(!"foo".method(:+).has_signature?) obj = object do def x() end signature :x end assert(obj.method(:x).has_signature?) end def test_signature() obj = object do def m0() end def m1(a) end def t(a,*) end end assert_equal([[], {}], obj.method(:m0).signature) assert_equal([[:any], {}], obj.method(:m1).signature) assert_equal([[:any], { :allow_trailing => true }], obj.method(:t).signature) sign = [[Fixnum, String], { :repeated => [1 .. 10, 2 .. 3] }] obj = object do def x(*) end signature(:x, *sign.flatten) end assert_equal(sign, obj.method(:x).signature) end end