# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.

# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
"""tests for the astng builder module
"""

import unittest
import sys
from os.path import join, abspath, dirname

from logilab.common.testlib import TestCase, unittest_main
from unittest_inference import get_name_node
from pprint import pprint
        
from logilab.astng.astutils import cvrtr
from logilab.astng import builder, nodes, Module, YES

import data
from data import module as test_module

class TransformerTC(TestCase):
        
    def setUp(self):
        transformer = builder.ASTNGTransformer()
        self.astng = transformer.parsesuite(open('data/format.py').read())

    def test_callfunc_lineno(self):
        stmts = self.astng.getChildNodes()[0].nodes
        # on line 4:
        #    function('aeozrijz\
        #    earzer', hop)
        discard = stmts[0]
        self.assertIsInstance(discard, nodes.Discard)
        self.assertEquals(discard.fromlineno, 4)
        self.assertEquals(discard.tolineno, 5)
        callfunc = discard.expr
        self.assertIsInstance(callfunc, nodes.CallFunc)
        self.assertEquals(callfunc.fromlineno, 4)
        self.assertEquals(callfunc.tolineno, 5)
        name = callfunc.node
        self.assertIsInstance(name, nodes.Name)
        self.assertEquals(name.fromlineno, 4)
        self.assertEquals(name.tolineno, 4)
        strarg = callfunc.args[0]
        self.assertIsInstance(strarg, nodes.Const)
        self.assertEquals(strarg.fromlineno, 5) # no way for this one (is 4 actually)
        self.assertEquals(strarg.tolineno, 5)
        namearg = callfunc.args[1]
        self.assertIsInstance(namearg, nodes.Name)
        self.assertEquals(namearg.fromlineno, 5)
        self.assertEquals(namearg.tolineno, 5)
        # on line 10:
        #    fonction(1,
        #             2,
        #             3,
        #             4)
        discard = stmts[2]
        self.assertIsInstance(discard, nodes.Discard)
        self.assertEquals(discard.fromlineno, 10)
        self.assertEquals(discard.tolineno, 13)
        callfunc = discard.expr
        self.assertIsInstance(callfunc, nodes.CallFunc)
        self.assertEquals(callfunc.fromlineno, 10)
        self.assertEquals(callfunc.tolineno, 13)
        name = callfunc.node
        self.assertIsInstance(name, nodes.Name)
        self.assertEquals(name.fromlineno, 10)
        self.assertEquals(name.tolineno, 10)
        for i, arg in enumerate(callfunc.args):
            self.assertIsInstance(arg, nodes.Const)
            self.assertEquals(arg.fromlineno, 10+i)
            self.assertEquals(arg.tolineno, 10+i)

    def test_function_lineno(self):
        stmts = self.astng.getChildNodes()[0].nodes
        # on line 15:
        #    def definition(a,
        #                   b,
        #                   c):
        #        return a + b + c
        function = stmts[3]
        self.assertIsInstance(function, nodes.Function)
        self.assertEquals(function.fromlineno, 15)
        self.assertEquals(function.tolineno, 17)
        code = function.code
        self.assertIsInstance(code, nodes.Stmt)
##         self.assertEquals(code.fromlineno, 18)
##         self.assertEquals(code.tolineno, 18)
        return_ = code.nodes[0]
        self.assertIsInstance(return_, nodes.Return)
        self.assertEquals(return_.fromlineno, 18)
        self.assertEquals(return_.tolineno, 18)

    def test_class_lineno(self):
        stmts = self.astng.getChildNodes()[0].nodes
        # on line 20:
        #    class debile(dict,
        #                 object):
        #       pass
        class_ = stmts[4]
        self.assertIsInstance(class_, nodes.Class)
        self.assertEquals(class_.fromlineno, 20)
        self.assertEquals(class_.tolineno, 21)
        code = class_.code
        self.assertIsInstance(code, nodes.Stmt)
##         self.assertEquals(code.fromlineno, 18)
##         self.assertEquals(code.tolineno, 18)
        pass_ = code.nodes[0]
        self.assertIsInstance(pass_, nodes.Pass)
        self.assertEquals(pass_.fromlineno, 22)
        self.assertEquals(pass_.tolineno, 22)

    def test_if_lineno(self):
        stmts = self.astng.getChildNodes()[0].nodes
        # on line 20:
        #    if aaaa: pass
        #    else:
        #        aaaa,bbbb = 1,2
        #        aaaa,bbbb = bbbb,aaaa
        if_ = stmts[5]
        self.assertIsInstance(if_, nodes.If)
        self.assertEquals(if_.fromlineno, 24)
        self.assertEquals(if_.tolineno, 24)
        else_ = if_.else_
        self.assertIsInstance(else_, nodes.Stmt)
        self.assertEquals(else_.fromlineno, 25)
        self.assertEquals(else_.tolineno, 27)

        
class BuilderTC(TestCase):
        
    def setUp(self):
        self.builder = builder.ASTNGBuilder()
        
    def test_border_cases(self):
        """check that a file with no trailing new line is parseable"""
        self.builder.file_build('data/noendingnewline.py', 'data.noendingnewline')
        self.assertRaises(builder.ASTNGBuildingException,
                          self.builder.file_build, 'data/inexistant.py', 'whatever')
        
    def test_inspect_build(self):
        """test astng tree build from a living object"""
        import __builtin__
        builtin_astng = self.builder.inspect_build(__builtin__)
        fclass = builtin_astng['file']
        self.assert_('name' in fclass)
        self.assert_('mode' in fclass)
        self.assert_('read' in fclass)
        self.assert_(fclass.newstyle)
        self.assert_(fclass.pytype(), '__builtin__.type')
        self.assert_(isinstance(fclass['read'], nodes.Function))
        self.assert_(isinstance(fclass['__doc__'], nodes.Const), fclass['__doc__'])
        # check builtin function has argnames == None
        dclass = builtin_astng['dict']
        self.assertEquals(dclass['has_key'].argnames, None)
        # just check type and object are there
        builtin_astng.getattr('type')
        builtin_astng.getattr('object')
        # check open file alias
        builtin_astng.getattr('open')
        # check 'help' is there (defined dynamically by site.py)
        builtin_astng.getattr('help')
        # check property has __init__
        pclass = builtin_astng['property']
        self.assert_('__init__' in pclass)
        # 
        import time
        time_astng = self.builder.module_build(time)
        self.assert_(time_astng)
        #
        unittest_astng = self.builder.inspect_build(unittest)
        self.failUnless(isinstance(builtin_astng['None'], nodes.Const), builtin_astng['None'])
        self.failUnless(isinstance(builtin_astng['Exception'], nodes.From), builtin_astng['Exception'])
        self.failUnless(isinstance(builtin_astng['NotImplementedError'], nodes.From))

        
    def test_inspect_build2(self):
        """test astng tree build from a living object"""
        try:
            from mx import DateTime
        except ImportError:
            self.skip('test skipped: mxDateTime is not available')
        else:
            dt_astng = self.builder.inspect_build(DateTime)
            dt_astng.getattr('DateTime')
            # this one is failing since DateTimeType.__module__ = 'builtins' !
            #dt_astng.getattr('DateTimeType')

    def test_inspect_build_instance(self):
        """test astng tree build from a living object"""
        import exceptions
        builtin_astng = self.builder.inspect_build(exceptions)
        fclass = builtin_astng['OSError']
        # things like OSError.strerror are now (2.5) data descriptors on the
        # class instead of entries in the __dict__ of an instance
        if sys.version_info < (2, 5):
            container = fclass.instance_attrs
        else:
            container = fclass
        self.assert_('errno' in container)
        self.assert_('strerror' in container)
        self.assert_('filename' in container)
            
    def test_package_name(self):
        """test base properties and method of a astng module"""
        datap = self.builder.file_build('data/__init__.py', 'data')
        self.assertEquals(datap.name, 'data')
        self.assertEquals(datap.package, 1)
        datap = self.builder.file_build('data/__init__.py', 'data.__init__')
        self.assertEquals(datap.name, 'data')
        self.assertEquals(datap.package, 1)

    def test_object(self):
        obj_astng = self.builder.inspect_build(object)
        self.failUnless('__setattr__' in obj_astng)

    def test_newstyle_detection(self):
        data = '''
class A:
    "old style"
        
class B(A):
    "old style"
        
class C(object):
    "new style"
        
class D(C):
    "new style"

__metaclass__ = type

class E(A):
    "old style"
    
class F:
    "new style"
'''
        mod_astng = self.builder.string_build(data, __name__, __file__)
        self.failIf(mod_astng['A'].newstyle)
        self.failIf(mod_astng['B'].newstyle)
        self.failUnless(mod_astng['C'].newstyle)
        self.failUnless(mod_astng['D'].newstyle)
        self.failIf(mod_astng['E'].newstyle)
        self.failUnless(mod_astng['F'].newstyle)

    def test_globals(self):
        data = '''
CSTE = 1

def update_global():
    global CSTE
    CSTE += 1

def global_no_effect():
    global CSTE2
    print CSTE
'''        
        astng = self.builder.string_build(data, __name__, __file__)
        self.failUnlessEqual(len(astng.getattr('CSTE')), 2)
        self.failUnlessEqual(astng.getattr('CSTE')[0].source_line(), 2)
        self.failUnlessEqual(astng.getattr('CSTE')[1].source_line(), 6)
        self.assertRaises(nodes.NotFoundError,
                          astng.getattr, 'CSTE2')
        self.assertRaises(nodes.InferenceError,
                          astng['global_no_effect'].ilookup('CSTE2').next)

    def test_socket_build(self):
        import socket
        astng = self.builder.module_build(socket)
        # XXX just check the first one. Actually 3 objects are infered (look at
        # the socket module) but the last one as those attributes dynamically
        # set and astng is missing this.
        for fclass in astng.igetattr('socket'):
            #print fclass.root().name, fclass.name, fclass.lineno
            self.assert_('connect' in fclass)
            self.assert_('send' in fclass)
            self.assert_('close' in fclass)
            break

    if sys.version_info >= (2, 4):
        def test_gen_expr_var_scope(self):
            data = 'l = list(n for n in range(10))\n'
            astng = self.builder.string_build(data, __name__, __file__)
            # n unvailable touside gen expr scope
            self.failIf('n' in astng)
            # test n is inferable anyway
            n = get_name_node(astng, 'n')
            self.failIf(n.scope() is astng)
            self.failUnlessEqual([i.__class__ for i in n.infer()],
                                 [YES.__class__])
        
        
class FileBuildTC(TestCase):

    def setUp(self):
        abuilder = builder.ASTNGBuilder()
        self.module = abuilder.file_build('data/module.py', 'data.module')

    def test_module_base_props(self):
        """test base properties and method of a astng module"""
        module = self.module
        self.assertEquals(module.name, 'data.module')
        self.assertEquals(module.doc, "test module for astng\n")
        self.assertEquals(module.source_line(), 0)
        self.assertEquals(module.parent, None)
        self.assertEquals(module.frame(), module)
        self.assertEquals(module.root(), module)
        self.assertEquals(module.file, join(abspath(data.__path__[0]), 'module.py'))
        self.assertEquals(module.pure_python, 1)
        self.assertEquals(module.package, 0)
        self.assert_(not module.is_statement())
        self.assertEquals(module.statement(), module)
        self.assertEquals(module.node.statement(), module)
        
    def test_module_locals(self):
        """test the 'locals' dictionary of a astng module"""
        module = self.module
        _locals = module.locals
        self.assertEquals(len(_locals), 17)
        self.assert_(_locals is module.globals)
        keys = _locals.keys()
        keys.sort()
        self.assertEquals(keys, ['MY_DICT', 'YO', 'YOUPI', '__dict__', 
                                 '__doc__', '__file__', '__name__', '__revision__',
                                 'clean', 'cvrtr', 'debuild', 'global_access',
                                 'modutils', 'nested_args', 'os', 'redirect',
                                 'spawn'])

    def test_function_base_props(self):
        """test base properties and method of a astng function"""
        module = self.module
        function = module['global_access']
        self.assertEquals(function.name, 'global_access')
        self.assertEquals(function.doc, 'function test')
        self.assertEquals(function.source_line(), 15)
        self.assert_(function.parent)
        self.assertEquals(function.frame(), function)
        self.assertEquals(function.parent.frame(), module)
        self.assertEquals(function.root(), module)
        self.assertEquals(function.argnames, ['key', 'val'])
        self.assertEquals(function.type, 'function')

    def test_function_locals(self):
        """test the 'locals' dictionary of a astng function"""
        _locals = self.module['global_access'].locals
        self.assertEquals(len(_locals), 4)
        keys = _locals.keys()
        keys.sort()
        self.assertEquals(keys, ['i', 'key', 'local', 'val'])
        
    def test_class_base_props(self):
        """test base properties and method of a astng class"""
        module = self.module
        klass = module['YO']
        self.assertEquals(klass.name, 'YO')
        self.assertEquals(klass.doc, 'hehe')
        self.assertEquals(klass.source_line(), 28)
        self.assert_(klass.parent)
        self.assertEquals(klass.frame(), klass)
        self.assertEquals(klass.parent.frame(), module)
        self.assertEquals(klass.root(), module)
        self.assertEquals(klass.basenames, [])
        self.assertEquals(klass.newstyle, False)
        self.failUnless(isinstance(klass['__doc__'], nodes.Const))
                        
    def test_class_locals(self):
        """test the 'locals' dictionary of a astng class"""
        module = self.module
        klass1 = module['YO']
        klass2 = module['YOUPI']
        locals1 = klass1.locals
        locals2 = klass2.locals
        keys = locals1.keys()
        keys.sort()
        self.assertEquals(keys, ['__dict__', '__doc__', '__init__', '__module__', '__name__',
                                 'a'])
        keys = locals2.keys()
        keys.sort()
        self.assertEquals(keys, ['__dict__', '__doc__', '__init__', '__module__', '__name__',
                                 'class_attr',
                                 'class_method', 'method', 'static_method'])

    def test_class_instance_attrs(self):
        module = self.module
        klass1 = module['YO']
        klass2 = module['YOUPI']
        self.assertEquals(klass1.instance_attrs.keys(), ['yo'])
        self.assertEquals(klass2.instance_attrs.keys(), ['member'])

    def test_class_basenames(self):
        module = self.module
        klass1 = module['YO']
        klass2 = module['YOUPI']
        self.assertEquals(klass1.basenames, [])
        self.assertEquals(klass2.basenames, ['YO'])
        
    def test_method_base_props(self):
        """test base properties and method of a astng method"""
        klass2 = self.module['YOUPI']
        # "normal" method
        method = klass2['method']
        self.assertEquals(method.name, 'method')
        self.assertEquals(method.argnames, ['self'])
        self.assertEquals(method.doc, 'method test')
        self.assertEquals(method.source_line(), 48)
        self.assertEquals(method.type, 'method')
        # class method
        method = klass2['class_method']
        self.assertEquals(method.argnames, ['cls'])
        self.assertEquals(method.type, 'classmethod')
        # static method
        method = klass2['static_method']
        self.assertEquals(method.argnames, [])
        self.assertEquals(method.type, 'staticmethod')

    def test_method_locals(self):
        """test the 'locals' dictionary of a astng method"""
        klass2 = self.module['YOUPI']
        method = klass2['method']
        _locals = method.locals
        self.assertEquals(len(_locals), 5)
        keys = _locals.keys()
        keys.sort()
        self.assertEquals(keys, ['a', 'autre', 'b', 'local', 'self'])

        
class ModuleBuildTC(FileBuildTC):

    def setUp(self):
        abuilder = builder.ASTNGBuilder()
        self.module = abuilder.module_build(test_module)


class InferedBuildTC(TestCase):

    def test(self):
        from logilab import astng
        import compiler
        sn = astng.MANAGER.astng_from_file(join(astng.__path__[0], 'scoped_nodes.py'))
        astastng = astng.MANAGER.astng_from_file(join(compiler.__path__[0], 'ast.py'))
        # check monkey patching of the compiler module has been infered
        lclass = list(astastng.igetattr('Lambda'))
        self.assertEquals(len(lclass), 1)
        lclass = lclass[0]
        self.assert_('as_string' in lclass.locals, lclass.locals.keys())
        self.assert_('format_args' in lclass.locals, lclass.locals.keys())
        self.assert_('type' in lclass.locals.keys())
        
#__all__ = ('BuilderModuleBuildTC', 'BuilderFileBuildTC', 'BuilderTC')

if __name__ == '__main__':
    unittest_main()
