#!/usr/bin/env python

#
# Test script for grouch.schema module
#

__revision__ = "$Id: test_schema.py,v 1.11 2001/08/21 22:20:28 gward Exp $"

import sys
from types import LongType, IntType, FileType

from mems.tools.unittest import TestScenario, parse_args, run_scenarios
from cStringIO import StringIO
from grouch.schema import ObjectSchema, ClassDefinition

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

tested_modules = [ "grouch.schema" ]


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 (TestScenario):

    def setup (self):
        pass

    def shutdown (self):
        pass

    test_cases = ['create', 'atomic', 'alias', 'classdef', 'gettype',
                  'pickle']

    def check_create (self):
        "create an object schema: 4"
        schema = ObjectSchema()
        self.test_bool("schema.is_atomic_type_name('int')")
        self.test_bool("schema.is_atomic_type_name('function')", want_true=0)
        self.test_val("schema.get_atomic_type_name(LongType)", "long")
        self.test_val("schema.get_atomic_type_object('long')", LongType)

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

        schema = ObjectSchema()
        int_type = schema.get_type(IntType)
        self.test_bool("int_type.is_atomic_type()")
        self.test_val("str(int_type)", "int")
        self.test_val("schema.get_type('int')", int_type)
        self.test_val("schema.get_type(int_type)", int_type)

        schema.add_atomic_type(sys.stdout)
        self.test_val("schema.get_atomic_type_name(FileType)", "file")
        self.test_val("schema.get_atomic_type_object('file')", FileType)
        self.test_val("schema.get_type('file').type", FileType)
        self.test_bool("schema.is_atomic_type_name('file')")
        self.test_bool("schema.is_atomic_type_name('thingy')", want_true=0)

        schema = ObjectSchema()
        schema.add_atomic_type(sys.stdout, "thingy")
        self.test_val("schema.get_atomic_type_name(FileType)", "thingy")
        self.test_val("schema.get_atomic_type_object('thingy')", FileType)
        self.test_val("schema.get_type('thingy').type", FileType)
        self.test_bool("schema.is_atomic_type_name('thingy')")
        self.test_bool("schema.is_atomic_type_name('file')", want_true=0)

        self.test_exc("schema.add_atomic_type(sys.stdout, 'file')",
                      ValueError)
        self.test_exc("schema.add_atomic_type(self.setup, 'thingy')",
                      ValueError)

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

        schema.add_alias("range", "(real,real)")
        self.test_bool("schema.has_alias('range')")
        self.test_val("schema.get_alias('range').container_type", "tuple")

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

        self.test_val("schema.get_class_definition('foo.Foo')", None)
        self.test_val("schema.get_class_definition(foo.Foo)", None)

        Foo_def = make_classdef("foo.Foo", schema)
        self.test_val("schema.get_class_definition('foo.Foo')", Foo_def)
        self.test_val("schema.get_class_definition(foo.Foo)", Foo_def)
        self.test_exc("schema.add_class(Foo_def)", RuntimeError)

    def check_gettype (self):
        "exercise the wildly overloaded 'get_type()' method: 9"
        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")

        self.test_val("schema.get_type(IntType).type", IntType)
        self.test_exc("schema.get_type(FunctionType)", ValueError)

        self.test_val("schema.get_type('int').type", IntType)
        self.test_val("schema.get_type('foo.Foo').klass_name","foo.Foo")
        self.test_val("schema.get_type('Foo').alias_type.klass_name","foo.Foo")
        self.test_exc("schema.get_type('blah')", ValueError)
        
        self.test_val("schema.get_type(foo.Foo).klass_name", "foo.Foo")
        int_type = schema.make_atomic_type('int')
        self.test_val("schema.get_type(int_type)", int_type, match_ident=1)

        self.test_exc("schema.get_type(1)", TypeError)

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

        # Redo the tests for a just-created schema on the recreated one
        self.test_bool("copy.is_atomic_type_name('int')")
        self.test_bool("copy.is_atomic_type_name('function')", want_true=0)
        self.test_val("copy.get_atomic_type_name(LongType)", "long")
        self.test_val("copy.get_atomic_type_object('long')", LongType)

        inttype = copy.get_type(IntType)
        longtype = copy.get_type(LongType)
        self.test_bool("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
        self.test_stmt("dumps(orig)")
        self.test_bool("orig.parser is oparser")
        copy = loads(dumps(orig))
        self.test_stmt("copy.parse_type('[int]')")

# class SchemaTest


class ClassDefTest (TestScenario):
    
    def setup (self):
        self.schema = ObjectSchema()
        self.int_type = self.schema.get_type('int')
        self.float_type = self.schema.get_type('float')

    def shutdown (self):
        pass


    test_cases = ['create', 'attrs', 'write']

    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: 4"
        # Hmmm, this'll be in trouble if ClassDefinition ever starts
        # to enforce that the named class must exist...
        cdef = ClassDefinition("Foo", self.schema)
        self.test_val("cdef.name", "Foo")
        self.test_val("cdef.bases", [])
        self.test_val("cdef.num_attributes()", 0)

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

    def check_attrs (self):
        "add/fetch attribute definitions: 18"
        Foo_def = self.make_classdef("Foo",
                                     attrs=[("a", self.int_type),
                                            ("b", self.float_type)])
        self.test_val("Foo_def.num_attributes()", 2)
        self.test_val("Foo_def.get_attribute_type('a')", self.int_type)

        # Should make this work eventually.
        self.test_exc("Foo_def.add_attribute('a', 'int')", TypeError)
        # But not this!
        self.test_exc("Foo_def.add_attribute('a', 42)", TypeError)

        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)])
        self.test_val("SubFoo_def._find_bases()",
                      [Foo_def, Bar_def, SubFoo_def])
        self.test_val("len(SubFoo_def.attrs)", 2)
        self.test_val("len(SubFoo_def.attr_types)", 2)
        self.test_val("SubFoo_def.num_attributes()", 5)
        self.test_val("SubFoo_def.get_attribute_type('a')", self.int_type)
        self.test_val("SubFoo_def.get_attribute_type('c')", self.int_type)
        self.test_val("SubFoo_def.get_attribute_type('d')", self.float_type)

        SubSubFoo_def = self.make_classdef("SubSubFoo",
                                           bases=["SubFoo"])
        self.test_val("SubSubFoo_def._find_bases()",
                      [Foo_def, Bar_def, SubFoo_def, SubSubFoo_def])
        self.test_val("len(SubSubFoo_def.attrs)", 0)
        self.test_val("len(SubSubFoo_def.attr_types)", 0)
        self.test_val("SubSubFoo_def.num_attributes()", 5)
        self.test_val("SubSubFoo_def.get_attribute_type('a')", self.int_type)
        self.test_val("SubSubFoo_def.get_attribute_type('c')", self.int_type)
        self.test_val("SubSubFoo_def.get_attribute_type('d')", self.float_type)

    def check_write (self):
        "write class definition: 3"
        
        cdef = self.make_classdef("Bar")
        expect = """\
class Bar:
    pass
"""
        f = StringIO() ; cdef.write(f)
        self.test_val("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)
        self.test_val("f.getvalue()", expect)

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

# class ClassDefTest


if __name__ == "__main__":
    (scenarios, options) = parse_args()
    run_scenarios (scenarios, options)
