#!/usr/bin/env python

#
# Test script for grouch.parsetype module
#

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

import sys
from types import IntType, StringType, FloatType
from mems.tools.unittest import TestScenario, parse_args, run_scenarios

from grouch.schema import ObjectSchema
from grouch.valuetype import ContainerType, UnionType
from grouch.type_parser import TypeScanner, TypeParser

tested_modules = [ "grouch.parser" ]

# Set up a fake module hierarchy:
#   module 'foo' provides class Foo (and sub-module bar)
#   module 'foo.bar' provides class FooBar
#   module '__main__' provides class Plain
import fake_module

_scanner = TypeScanner()
_parser = TypeParser()

# Emulate the old standalone 'parse_type()' function.
def parse_type (typestr, schema):
    global _scanner, _parser
    _parser.set_schema(schema)
    return _parser.parse(_scanner.tokenize(typestr))


class ParseTypeTest (TestScenario):

    def setup (self):
        self.schema = fake_module.make_schema()

    def shutdown (self):
        del self.schema


    test_cases = ['atomic',
                  'misc',
                  'instance',
                  'list',
                  'tuple',
                  'dict',
                  'instcont',
                  'recursive_1',
                  'recursive_2',
                  'errors',
                  'alias']


    def check_atomic (self):
        "parse atomic and 'any' types: 6"
        t = parse_type('int', self.schema)
        self.test_val("str(t)", "int")
        self.test_bool("t.is_atomic_type()")
        self.test_val("t.type", IntType)

        self.test_exc("parse_type('list', self.schema)", ValueError)
        self.test_exc("parse_type('foo', self.schema)", ValueError)
        self.test_exc("parse_type(42, self.schema)", TypeError)

    def check_misc (self):
        "parse miscellaneous types (any, boolean): 7"
        t = parse_type('any', self.schema)
        self.test_bool("t.is_any_type()")

        t = parse_type('boolean', self.schema)
        self.test_bool("t.is_boolean_type()")

        t = parse_type('[boolean]', self.schema)
        self.test_val("str(t)", "[boolean]")
        self.test_bool("t.get_element_type().is_boolean_type()")

        t = parse_type('{boolean : (any,any)}', self.schema)
        self.test_val("str(t)", "{ boolean : (any, any) }")
        self.test_bool("t.get_key_type().is_boolean_type()")
        self.test_bool("t.get_value_type().get_element_types()[0]."
                       "is_any_type()")

    def check_instance (self):
        "parse instance types: 9"
        from foo import Foo, bar

        t = parse_type('foo.Foo', self.schema)
        self.test_val("t.klass_name", "foo.Foo")
        self.test_val("str(t)", "foo.Foo")
        self.test_bool("t.is_instance_type()")

        t = parse_type('foo.bar.FooBar', self.schema)
        self.test_val("t.klass_name", "foo.bar.FooBar")
        self.test_val("str(t)", "foo.bar.FooBar")

        self.test_exc("parse_type('bar.FooBar', self.schema)", ValueError)
        self.test_exc("parse_type('Foo', self.schema)", ValueError)

        t = parse_type('Plain', self.schema)
        self.test_val("t.klass_name", "Plain")
        self.test_val("str(t)", "Plain")

    def check_list (self):
        "parse simple list types: 18"
        t = parse_type('[int]', self.schema)
        self.test_bool("t.is_container_type()")
        self.test_val("t.element_type.type", IntType)
        self.test_val("str(t)", "[int]")

        self.test_exc("parse_type('[StringType]', self.schema)", ValueError)
        self.test_exc("parse_type('[list]', self.schema)", ValueError)
        self.test_exc("parse_type('[string', self.schema)", ValueError)

        t = parse_type('[foo.Foo]', self.schema)
        self.test_bool("t.is_container_type()")
        self.test_bool("t.element_type.is_instance_type()")
        self.test_val("str(t)", "[foo.Foo]")

        self.test_exc("parse_type('[foo.FooBar]', self.schema)", ValueError)
        t = parse_type('[Plain]', self.schema)
        self.test_val("str(t)", "[Plain]")
        self.test_val("t.get_element_type().get_class_name()", "Plain")

        # should allow empty list (tuple, dict) someday, but for now we don't
        self.test_exc("parse_type('[]', self.schema)", ValueError)
        self.test_exc("parse_type('[int, string]', self.schema)", ValueError)

        # but as long as [any] works, [] isn't really needed!
        t = parse_type('[any]', self.schema)
        self.test_val("str(t)", "[any]")
        self.test_bool("t.is_container_type()")
        self.test_bool("t.is_any_type()", want_true=0)
        self.test_bool("t.get_element_type().is_any_type()")

    def check_tuple (self):
        "parse simple tuple types: 21"
        t = parse_type('(string,)', self.schema)
        self.test_val("str(t)", "(string,)")
        self.test_bool("t.is_container_type()")
        self.test_val("t.container_type", "tuple")
        self.test_val("len(t.element_types)", 1)
        self.test_val("t.element_types[0].type", StringType)

        t = parse_type('(int,string)', self.schema)
        self.test_val("str(t)", "(int, string)")
        self.test_bool("t.is_extended()", want_true=0)
        self.test_val("len(t.element_types)", 2)
        self.test_val("t.element_types[0].type", IntType)
        self.test_val("t.element_types[1].type", StringType)

        t = parse_type('(int, string*)', self.schema)
        self.test_val("str(t)", "(int, string*)")
        self.test_bool("t.is_extended()")
        self.test_val("len(t.element_types)", 2)
        self.test_val("t.element_types[0].type", IntType)
        self.test_val("t.element_types[1].type", StringType)

        self.test_exc("parse_type('(IntType)', self.schema)", ValueError)
        self.test_exc("parse_type('(IntType,)', self.schema)", ValueError)
        self.test_exc("parse_type('(list)', self.schema)", ValueError)

        self.test_exc("parse_type('()', self.schema)", ValueError)
        t = parse_type('(any, string)', self.schema)
        self.test_val("str(t)", "(any, string)")
        self.test_bool("t.get_element_types()[0].is_any_type()")

    def check_dict (self):
        "parse simple dictionary types: 12"
        t = parse_type('{float: string}', self.schema)
        self.test_val("str(t)", "{ float : string }")
        self.test_bool("t.is_container_type()")
        self.test_val("t.container_type", "dictionary")
        self.test_val("t.key_type.type", FloatType)
        self.test_val("t.value_type.type", StringType)

        self.test_exc("parse_type('{float:}', self.schema)", ValueError)
        self.test_exc("parse_type('{string: float ', self.schema)", ValueError)
        self.test_exc("parse_type('{IntType : string}', self.schema)", ValueError)

        self.test_exc("parse_type('{}', self.schema)", ValueError)
        self.test_exc("parse_type('{int:string, int:float}', self.schema)", ValueError)

        t = parse_type('{string : any}', self.schema)
        self.test_val("str(t)", "{ string : any }")
        self.test_bool("t.get_value_type().is_any_type()")

    def check_instcont (self):
        "parse instance-container types: 15"
        from foo import Foo
        t = parse_type('foo.Foo [int]', self.schema)
        self.test_bool("t.is_instance_type()")
        self.test_bool("t.is_container_type()")
        self.test_bool("t.is_instance_container_type()")
        self.test_val("str(t)", "foo.Foo [int]")
        self.test_val("t.get_class_name()", "foo.Foo")
        self.test_val("t.get_element_type().type", IntType)

        t = parse_type('foo.Foo (float,string)', self.schema)
        self.test_val("str(t)", "foo.Foo (float, string)")
        self.test_val("t.get_class_name()", "foo.Foo")
        self.test_val("[t.type for t in t.get_element_types()]",
                      [FloatType, StringType])

        t = parse_type('foo.Foo {string:int}', self.schema)
        self.test_val("str(t)", "foo.Foo { string : int }")
        self.test_val("t.get_class_name()", "foo.Foo")
        self.test_val("t.get_item_types()[0].get_type()", StringType)
        self.test_val("t.get_item_types()[1].get_type()", IntType)

        # An instance-container that contains a container
        t = parse_type('foo.Foo {string : (int,string)}', self.schema)
        self.test_val("str(t)", "foo.Foo { string : (int, string) }")
        # And one that contains another instance-container
        t = parse_type('foo.Foo { int : foo.bar.FooBar [string] }',
                       self.schema)
        self.test_val("str(t)", "foo.Foo { int : foo.bar.FooBar [string] }")

    def check_recursive_1 (self):
        "parse simple recursive type specifications: 11"
        t = parse_type('[ (int,float) ]', self.schema)
        self.test_val("str(t)", "[(int, float)]")
        self.test_bool("t.is_container_type()")
        subtype = t.element_type
        self.test_bool("subtype.is_container_type()")
        self.test_bool("(subtype.element_types[0].type,"
                       " subtype.element_types[1].type)",
                       (IntType, FloatType))

        t = parse_type('int | [string]', self.schema)
        self.test_val("str(t)", "int | [string]")
        self.test_bool("t.is_union_type()")
        self.test_val("len(t.union_types)", 2)
        (alt1, alt2) = t.union_types
        self.test_val("alt1.type", IntType)
        self.test_bool("alt2.is_container_type()")
        self.test_val("alt2.container_type", "list")
        self.test_val("alt2.element_type.type", StringType)

    def check_recursive_2 (self):
        "parse complex recursive type specifications: 8"

        # Of course, anyone who would use a dictionary mapping
        # (int,string) tuples to either foo.Foo or foo.bar.FooBar
        # instances in production code should be shot... ;-)

        t1 = parse_type("{(int,string):foo.Foo|foo.bar.FooBar}", self.schema)
        self.test_val("str(t1)",
                      "{ (int, string) : foo.Foo | foo.bar.FooBar }")
        (keytype, valuetype) = t1.get_item_types()
        self.test_val("str(keytype)", "(int, string)")
        self.test_val("str(valuetype)", "foo.Foo | foo.bar.FooBar")

        # Now this is just silly.
        t2 = parse_type("[{float|long : [(string, foo.Foo)]}]", self.schema)
        self.test_val("str(t2)",
                      "[{ float | long : [(string, foo.Foo)] }]")
        eltype = t2.element_type
        (keytype, valuetype) = eltype.get_item_types()
        self.test_val("str(keytype)", "float | long")
        self.test_val("str(valuetype)", "[(string, foo.Foo)]")
        
        # And this is plain gratuitous.
        t = UnionType(self.schema)
        t.set_union_types([t1, t2])
        self.test_val("str(t)", "%s | %s" % (t1, t2))

        # Not much better
        t = self.schema.make_tuple_type((t1, parse_type("int", self.schema)))
        self.test_val("str(t)", "(%s, int)" % t1)

    def check_errors (self):
        "parse erroneous names and dotted-named in various contexts: 12"
        # Mainly for checking the error messages and ensuring that we
        # hit every branch of 'parse_type()' and 'create_subtype()'
        msg_fmt = ("invalid type '%s': "
                   "not an atomic type, alias, or known class")
        exc = self.test_exc("parse_type('FooBar', self.schema)",
                            ValueError)
        self.test_val("str(exc)", msg_fmt % "FooBar")

        exc = self.test_exc("parse_type('foo.bar', self.schema)",
                            ValueError)
        self.test_val("str(exc)", msg_fmt % "foo.bar")

        exc = self.test_exc("parse_type('[FooBar]', self.schema)",
                            ValueError)
        self.test_val("str(exc)", msg_fmt % "FooBar")

        exc = self.test_exc("parse_type('FooBar|int', self.schema)",
                            ValueError)
        self.test_val("str(exc)", msg_fmt % "FooBar")

        exc = self.test_exc("parse_type('int|FooBar', self.schema)",
                            ValueError)
        self.test_val("str(exc)", msg_fmt % "FooBar")

        exc = self.test_exc("parse_type('[ding.dong]', self.schema)",
                            ValueError)
        self.test_val("str(exc)", msg_fmt % "ding.dong")

    def check_alias (self):
        "parse types with aliases: 11"
        schema = self.schema
        schema.add_alias("real", "int|float|long")
        schema.add_alias("range", "(real,real)")

        t = parse_type('{string:range}', schema)
        self.test_val("str(t)", "{ string : range }")
        #self.test_val("t.expand_aliases()",
        #              "{ string : (int | float | long, int | float | long) }")
        self.test_bool("t.is_alias_type()", want_true=0)

        vtype = t.get_value_type()
        self.test_bool("vtype.is_alias_type()")
        self.test_val("str(vtype)", "range")
        self.test_val("vtype.get_alias_type().container_type", "tuple")
        etypes = vtype.get_alias_type().get_element_types()
        self.test_bool("etypes[0].is_alias_type()")
        self.test_val("etypes[0].get_alias_type()", schema.get_alias("real"))
        self.test_bool("etypes[0].get_alias_type().is_union_type()")

        from foo import Foo
        schema.add_alias("Foo", "foo.Foo")
        t = parse_type('Foo [int]', schema)
        self.test_bool("t.is_instance_container_type()")
        self.test_val("t.get_class_name()", "foo.Foo")
        self.test_val("t.get_element_type().get_type()", IntType)


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



