#!/usr/bin/env python
"""
$URL: svn+ssh://svn/repos/trunk/grouch/lib/test/utest_schema.py $
$Id: utest_schema.py 25104 2004-09-14 12:41:32Z dbinger $
"""

import sys
from types import LongType, IntType, FileType

from sancho.utest import UTest
from cStringIO import StringIO
from grouch.schema import ObjectSchema, ClassDefinition

from grouch.test import fake_module     # sets up foo.Foo, foo.SubFoo,
                                        # and foo.bar.FooBar modules/classes



def make_classdef (name, schema, bases=None, attrs=None):
    # name : string
    # bases : [string]
    # attrs : [(string, ValueType)]

    classdef = ClassDefinition(name, schema, bases)
    if attrs:
        for (name, type) in attrs:
            classdef.add_attribute(name, type)
    classdef.finish_definition()
    schema.add_class(classdef)

    return classdef


class SchemaTest (UTest):

    def _pre (self):
        pass

    def _post (self):
        pass

    def check_create (self):
        "create an object schema"
        schema = ObjectSchema()
        assert schema.is_atomic_type_name('int')
        assert not schema.is_atomic_type_name('function')
        assert schema.get_atomic_type_name(LongType) == "long"
        assert schema.get_atomic_type_object('long') == LongType

    def check_atomic (self):
        "add/fetch atomic types to/from a schema"

        schema = ObjectSchema()
        int_type = schema.get_type(IntType)
        assert int_type.is_atomic_type()
        assert str(int_type) == "int"
        assert schema.get_type('int') == int_type
        assert schema.get_type(int_type) == int_type

        schema.add_atomic_type(sys.__stdout__, 'file')
        assert schema.get_atomic_type_name(FileType) == "file"
        assert schema.get_atomic_type_object('file') == FileType
        assert schema.get_type('file').type == FileType
        assert schema.is_atomic_type_name('file')
        assert not schema.is_atomic_type_name('thingy')

        schema = ObjectSchema()
        schema.add_atomic_type(sys.__stdout__, "thingy")
        assert schema.get_atomic_type_name(FileType) == "thingy"
        assert schema.get_atomic_type_object('thingy') == FileType
        assert schema.get_type('thingy').type == FileType
        assert schema.is_atomic_type_name('thingy')
        assert not schema.is_atomic_type_name('file')

        try:
            schema.add_atomic_type(sys.__stdout__, 'file')
            assert 0
        except ValueError: pass
        try:
            schema.add_atomic_type(self._pre, 'thingy')
            assert 0
        except ValueError: pass

    def check_alias (self):
        "adding aliases to a schema"
        schema = ObjectSchema()
        schema.add_alias("real", "int|float|long")
        schema.add_alias("pvalue", "(float,string)")
        assert schema.has_alias('real')
        assert schema.get_alias('real').is_union_type()

        schema.add_alias("range", "(real,real)")
        assert schema.has_alias('range')
        assert schema.get_alias('range').container_type == "tuple"

    def check_classdef (self):
        "add/fetch class definitions to/from a schema"
        import foo
        schema = ObjectSchema()

        assert schema.get_class_definition('foo.Foo') == None
        assert schema.get_class_definition(foo.Foo) == None

        Foo_def = make_classdef("foo.Foo", schema)
        assert schema.get_class_definition('foo.Foo') == Foo_def
        assert schema.get_class_definition(foo.Foo) == Foo_def
        try:
            schema.add_class(Foo_def)
            assert 0
        except RuntimeError: pass

    def check_gettype (self):
        "exercise the wildly overloaded 'get_type()' method"
        from types import FunctionType  # not an atomic type!
        import foo

        schema = fake_module.make_schema() # so we have some classes defined
        schema.add_alias("Foo", "foo.Foo")

        assert schema.get_type(IntType).type == IntType
        try:
            schema.get_type(FunctionType)
            assert 0
        except ValueError: pass

        assert schema.get_type('int').type == IntType
        assert schema.get_type('foo.Foo').klass_name == "foo.Foo"
        assert schema.get_type('Foo').alias_type.klass_name == "foo.Foo"
        try:
            schema.get_type('blah')
            assert 0
        except ValueError: pass

        assert schema.get_type(foo.Foo).klass_name == "foo.Foo"
        int_type = schema.make_atomic_type('int')
        assert schema.get_type(int_type) is int_type

        try:
            schema.get_type(1)
            assert 0
        except TypeError: pass

    def check_pickle (self):
        "pickle an object schema"
        from cPickle import loads, dumps
        orig = ObjectSchema()
        copy = loads(dumps(orig))

        # Redo the tests for a just-created schema on the recreated one
        assert copy.is_atomic_type_name('int')
        assert not copy.is_atomic_type_name('function')
        assert copy.get_atomic_type_name(LongType) == "long"
        assert copy.get_atomic_type_object('long') == LongType

        inttype = copy.get_type(IntType)
        longtype = copy.get_type(LongType)
        assert inttype.schema is longtype.schema

        # Make sure we can pickle a schema with scanner and parser objects
        # (SPARK classes don't seem to be pickleable).
        orig.parse_type("[int]")
        oparser = orig.parser
        dumps(orig)
        assert orig.parser is oparser
        copy = loads(dumps(orig))
        copy.parse_type('[int]')

# class SchemaTest


class ClassDefTest (UTest):

    def _pre (self):
        self.schema = ObjectSchema()
        self.int_type = self.schema.get_type('int')
        self.float_type = self.schema.get_type('float')

    def _post (self):
        pass

    def _make_classdef (self, name, bases=None, attrs=None):
        return make_classdef(name, self.schema, bases, attrs)


    def check_create (self):
        "create a class definition"
        # Hmmm, this'll be in trouble if ClassDefinition ever starts
        # to enforce that the named class must exist...
        cdef = ClassDefinition("Foo", self.schema)
        assert cdef.name == "Foo"
        assert cdef.bases == []
        assert cdef.num_attributes() == 0

        cdef = ClassDefinition("SubFoo", self.schema, ["Foo"])
        assert cdef.bases == ["Foo"]

    def check_attrs (self):
        "add/fetch attribute definitions"
        Foo_def = self._make_classdef("Foo",
                                     attrs=[("a", self.int_type),
                                            ("__b", self.float_type)])
        assert Foo_def.num_attributes() == 2
        assert Foo_def.get_attribute_type('a') == self.int_type
        assert Foo_def.get_attribute_type('_Foo__b') == self.float_type

        # Should make this work eventually.
        try:
            Foo_def.add_attribute('x', 'int')
            assert 0
        except TypeError: pass
        # But not this!
        try:
            Foo_def.add_attribute('x', 42)
            assert 0
        except TypeError: pass

        Bar_def = self._make_classdef("Bar", attrs=[("c", self.int_type)])
        SubFoo_def = self._make_classdef("SubFoo",
                                        bases=["Foo", "Bar"],
                                        attrs=[("d", self.float_type),
                                               ("e", self.int_type)])
        assert SubFoo_def._find_bases() == [Foo_def, Bar_def, SubFoo_def]
        assert len(SubFoo_def.attrs) == 2
        assert len(SubFoo_def.attr_types) == 2
        assert SubFoo_def.num_attributes() == 5
        assert SubFoo_def.get_attribute_type('a') == self.int_type
        assert SubFoo_def.get_attribute_type('_Foo__b') == self.float_type
        assert SubFoo_def.get_attribute_type('c') == self.int_type
        assert SubFoo_def.get_attribute_type('d') == self.float_type
        SubSubFoo_def = self._make_classdef("SubSubFoo", bases=["SubFoo"])
        assert SubSubFoo_def._find_bases() == [
            Foo_def, Bar_def, SubFoo_def, SubSubFoo_def]
        assert len(SubSubFoo_def.attrs) == 0
        assert len(SubSubFoo_def.attr_types) == 0
        assert SubSubFoo_def.num_attributes() == 5
        assert SubSubFoo_def.get_attribute_type('a') == self.int_type
        assert SubSubFoo_def.get_attribute_type('c') == self.int_type
        assert SubSubFoo_def.get_attribute_type('d') == self.float_type

    def check_write (self):
        "write class definition"

        cdef = self._make_classdef("Bar")
        expect = """\
class Bar:
    pass
"""
        f = StringIO() ; cdef.write(f)
        assert f.getvalue() == expect

        cdef = self._make_classdef("Foo",
                                   attrs=[("a", self.int_type),
                                          ("b", self.float_type)])
        expect = """\
class Foo:
    a : int
    b : float
"""
        f = StringIO() ; cdef.write(f)
        assert f.getvalue() == expect

        cdef = self._make_classdef("SubFoo", bases=["Foo"])
        expect = """\
class SubFoo (Foo):
    pass
"""
        f = StringIO() ; cdef.write(f)
        assert f.getvalue() == expect

# class ClassDefTest


if __name__ == "__main__":
    SchemaTest()
    ClassDefTest()
