import unittest

from test.test_support import check_syntax_error, run_unittest



import warnings

warnings.filterwarnings("ignore", r"import \*", SyntaxWarning, "<test string>")

warnings.filterwarnings("ignore", r"import \*", SyntaxWarning, "<string>")



class ScopeTests(unittest.TestCase):



    def testSimpleNesting(self):



        def make_adder(x):

            def adder(y):

                return x + y

            return adder



        inc = make_adder(1)

        plus10 = make_adder(10)



        self.assertEqual(inc(1), 2)

        self.assertEqual(plus10(-2), 8)



    def testExtraNesting(self):



        def make_adder2(x):

            def extra(): # check freevars passing through non-use scopes

                def adder(y):

                    return x + y

                return adder

            return extra()



        inc = make_adder2(1)

        plus10 = make_adder2(10)



        self.assertEqual(inc(1), 2)

        self.assertEqual(plus10(-2), 8)



    def testSimpleAndRebinding(self):



        def make_adder3(x):

            def adder(y):

                return x + y

            x = x + 1 # check tracking of assignment to x in defining scope

            return adder



        inc = make_adder3(0)

        plus10 = make_adder3(9)



        self.assertEqual(inc(1), 2)

        self.assertEqual(plus10(-2), 8)



    def testNestingGlobalNoFree(self):



        def make_adder4(): # XXX add exta level of indirection

            def nest():

                def nest():

                    def adder(y):

                        return global_x + y # check that plain old globals work

                    return adder

                return nest()

            return nest()



        global_x = 1

        adder = make_adder4()

        self.assertEqual(adder(1), 2)



        global_x = 10

        self.assertEqual(adder(-2), 8)



    def testNestingThroughClass(self):



        def make_adder5(x):

            class Adder:

                def __call__(self, y):

                    return x + y

            return Adder()



        inc = make_adder5(1)

        plus10 = make_adder5(10)



        self.assertEqual(inc(1), 2)

        self.assertEqual(plus10(-2), 8)



    def testNestingPlusFreeRefToGlobal(self):



        def make_adder6(x):

            global global_nest_x

            def adder(y):

                return global_nest_x + y

            global_nest_x = x

            return adder



        inc = make_adder6(1)

        plus10 = make_adder6(10)



        self.assertEqual(inc(1), 11) # there's only one global

        self.assertEqual(plus10(-2), 8)



    def testNearestEnclosingScope(self):



        def f(x):

            def g(y):

                x = 42 # check that this masks binding in f()

                def h(z):

                    return x + z

                return h

            return g(2)



        test_func = f(10)

        self.assertEqual(test_func(5), 47)



    def testMixedFreevarsAndCellvars(self):



        def identity(x):

            return x



        def f(x, y, z):

            def g(a, b, c):

                a = a + x # 3

                def h():

                    # z * (4 + 9)

                    # 3 * 13

                    return identity(z * (b + y))

                y = c + z # 9

                return h

            return g



        g = f(1, 2, 3)

        h = g(2, 4, 6)

        self.assertEqual(h(), 39)



    def testFreeVarInMethod(self):



        def test():

            method_and_var = "var"

            class Test:

                def method_and_var(self):

                    return "method"

                def test(self):

                    return method_and_var

                def actual_global(self):

                    return str("global")

                def str(self):

                    return str(self)

            return Test()



        t = test()

        self.assertEqual(t.test(), "var")

        self.assertEqual(t.method_and_var(), "method")

        self.assertEqual(t.actual_global(), "global")



        method_and_var = "var"

        class Test:

            # this class is not nested, so the rules are different

            def method_and_var(self):

                return "method"

            def test(self):

                return method_and_var

            def actual_global(self):

                return str("global")

            def str(self):

                return str(self)



        t = Test()

        self.assertEqual(t.test(), "var")

        self.assertEqual(t.method_and_var(), "method")

        self.assertEqual(t.actual_global(), "global")



    def testRecursion(self):



        def f(x):

            def fact(n):

                if n == 0:

                    return 1

                else:

                    return n * fact(n - 1)

            if x >= 0:

                return fact(x)

            else:

                raise ValueError, "x must be >= 0"



        self.assertEqual(f(6), 720)





    def testUnoptimizedNamespaces(self):



        check_syntax_error(self, """\

def unoptimized_clash1(strip):

    def f(s):

        from string import *

        return strip(s) # ambiguity: free or local

    return f

""")



        check_syntax_error(self, """\

def unoptimized_clash2():

    from string import *

    def f(s):

        return strip(s) # ambiguity: global or local

    return f

""")



        check_syntax_error(self, """\

def unoptimized_clash2():

    from string import *

    def g():

        def f(s):

            return strip(s) # ambiguity: global or local

        return f

""")



        # XXX could allow this for exec with const argument, but what's the point

        check_syntax_error(self, """\

def error(y):

    exec "a = 1"

    def f(x):

        return x + y

    return f

""")



        check_syntax_error(self, """\

def f(x):

    def g():

        return x

    del x # can't del name

""")



        check_syntax_error(self, """\

def f():

    def g():

        from string import *

        return strip # global or local?

""")



        # and verify a few cases that should work



        exec """

def noproblem1():

    from string import *

    f = lambda x:x



def noproblem2():

    from string import *

    def f(x):

        return x + 1



def noproblem3():

    from string import *

    def f(x):

        global y

        y = x

"""



    def testLambdas(self):



        f1 = lambda x: lambda y: x + y

        inc = f1(1)

        plus10 = f1(10)

        self.assertEqual(inc(1), 2)

        self.assertEqual(plus10(5), 15)



        f2 = lambda x: (lambda : lambda y: x + y)()

        inc = f2(1)

        plus10 = f2(10)

        self.assertEqual(inc(1), 2)

        self.assertEqual(plus10(5), 15)



        f3 = lambda x: lambda y: global_x + y

        global_x = 1

        inc = f3(None)

        self.assertEqual(inc(2), 3)



        f8 = lambda x, y, z: lambda a, b, c: lambda : z * (b + y)

        g = f8(1, 2, 3)

        h = g(2, 4, 6)

        self.assertEqual(h(), 18)



    def testUnboundLocal(self):



        def errorInOuter():

            print y

            def inner():

                return y

            y = 1



        def errorInInner():

            def inner():

                return y

            inner()

            y = 1



        try:

            errorInOuter()

        except UnboundLocalError:

            pass

        else:

            self.fail()



        try:

            errorInInner()

        except NameError:

            pass

        else:

            self.fail()



        # test for bug #1501934: incorrect LOAD/STORE_GLOBAL generation

        exec """

global_x = 1

def f():

    global_x += 1

try:

    f()

except UnboundLocalError:

    pass

else:

    fail('scope of global_x not correctly determined')

""" in {'fail': self.fail}



    def testComplexDefinitions(self):



        def makeReturner(*lst):

            def returner():

                return lst

            return returner



        self.assertEqual(makeReturner(1,2,3)(), (1,2,3))



        def makeReturner2(**kwargs):

            def returner():

                return kwargs

            return returner



        self.assertEqual(makeReturner2(a=11)()['a'], 11)



        def makeAddPair((a, b)):

            def addPair((c, d)):

                return (a + c, b + d)

            return addPair



        self.assertEqual(makeAddPair((1, 2))((100, 200)), (101,202))



    def testScopeOfGlobalStmt(self):

# Examples posted by Samuele Pedroni to python-dev on 3/1/2001



        exec """\

# I

x = 7

def f():

    x = 1

    def g():

        global x

        def i():

            def h():

                return x

            return h()

        return i()

    return g()

self.assertEqual(f(), 7)

self.assertEqual(x, 7)



# II

x = 7

def f():

    x = 1

    def g():

        x = 2

        def i():

            def h():

                return x

            return h()

        return i()

    return g()

self.assertEqual(f(), 2)

self.assertEqual(x, 7)



# III

x = 7

def f():

    x = 1

    def g():

        global x

        x = 2

        def i():

            def h():

                return x

            return h()

        return i()

    return g()

self.assertEqual(f(), 2)

self.assertEqual(x, 2)



# IV

x = 7

def f():

    x = 3

    def g():

        global x

        x = 2

        def i():

            def h():

                return x

            return h()

        return i()

    return g()

self.assertEqual(f(), 2)

self.assertEqual(x, 2)



# XXX what about global statements in class blocks?

# do they affect methods?



x = 12

class Global:

    global x

    x = 13

    def set(self, val):

        x = val

    def get(self):

        return x



g = Global()

self.assertEqual(g.get(), 13)

g.set(15)

self.assertEqual(g.get(), 13)

"""



    def testLeaks(self):



        class Foo:

            count = 0



            def __init__(self):

                Foo.count += 1



            def __del__(self):

                Foo.count -= 1



        def f1():

            x = Foo()

            def f2():

                return x

            f2()



        for i in range(100):

            f1()



        self.assertEqual(Foo.count, 0)



    def testClassAndGlobal(self):



        exec """\

def test(x):

    class Foo:

        global x

        def __call__(self, y):

            return x + y

    return Foo()



x = 0

self.assertEqual(test(6)(2), 8)

x = -1

self.assertEqual(test(3)(2), 5)



looked_up_by_load_name = False

class X:

    # Implicit globals inside classes are be looked up by LOAD_NAME, not

    # LOAD_GLOBAL.

    locals()['looked_up_by_load_name'] = True

    passed = looked_up_by_load_name



self.assert_(X.passed)

"""



    def testLocalsFunction(self):



        def f(x):

            def g(y):

                def h(z):

                    return y + z

                w = x + y

                y += 3

                return locals()

            return g



        d = f(2)(4)

        self.assert_(d.has_key('h'))

        del d['h']

        self.assertEqual(d, {'x': 2, 'y': 7, 'w': 6})



    def testLocalsClass(self):

        # This test verifies that calling locals() does not pollute

        # the local namespace of the class with free variables.  Old

        # versions of Python had a bug, where a free variable being

        # passed through a class namespace would be inserted into

        # locals() by locals() or exec or a trace function.

        #

        # The real bug lies in frame code that copies variables

        # between fast locals and the locals dict, e.g. when executing

        # a trace function.



        def f(x):

            class C:

                x = 12

                def m(self):

                    return x

                locals()

            return C



        self.assertEqual(f(1).x, 12)



        def f(x):

            class C:

                y = x

                def m(self):

                    return x

                z = list(locals())

            return C



        varnames = f(1).z

        self.assert_("x" not in varnames)

        self.assert_("y" in varnames)



    def testLocalsClass_WithTrace(self):

        # Issue23728: after the trace function returns, the locals()

        # dictionary is used to update all variables, this used to

        # include free variables. But in class statements, free

        # variables are not inserted...

        import sys

        sys.settrace(lambda a,b,c:None)

        try:

            x = 12



            class C:

                def f(self):

                    return x



            self.assertEquals(x, 12) # Used to raise UnboundLocalError

        finally:

            sys.settrace(None)



    def testBoundAndFree(self):

        # var is bound and free in class



        def f(x):

            class C:

                def m(self):

                    return x

                a = x

            return C



        inst = f(3)()

        self.assertEqual(inst.a, inst.m())



    def testInteractionWithTraceFunc(self):



        import sys

        def tracer(a,b,c):

            return tracer



        def adaptgetter(name, klass, getter):

            kind, des = getter

            if kind == 1:       # AV happens when stepping from this line to next

                if des == "":

                    des = "_%s__%s" % (klass.__name__, name)

                return lambda obj: getattr(obj, des)



        class TestClass:

            pass



        sys.settrace(tracer)

        adaptgetter("foo", TestClass, (1, ""))

        sys.settrace(None)



        self.assertRaises(TypeError, sys.settrace)



    def testEvalExecFreeVars(self):



        def f(x):

            return lambda: x + 1



        g = f(3)

        self.assertRaises(TypeError, eval, g.func_code)



        try:

            exec g.func_code in {}

        except TypeError:

            pass

        else:

            self.fail("exec should have failed, because code contained free vars")



    def testListCompLocalVars(self):



        try:

            print bad

        except NameError:

            pass

        else:

            print "bad should not be defined"



        def x():

            [bad for s in 'a b' for bad in s.split()]



        x()

        try:

            print bad

        except NameError:

            pass



    def testEvalFreeVars(self):



        def f(x):

            def g():

                x

                eval("x + 1")

            return g



        f(4)()



    def testFreeingCell(self):

        # Test what happens when a finalizer accesses

        # the cell where the object was stored.

        class Special:

            def __del__(self):

                nestedcell_get()



        def f():

            global nestedcell_get

            def nestedcell_get():

                return c



            c = (Special(),)

            c = 2



        f() # used to crash the interpreter...



    def testGlobalInParallelNestedFunctions(self):

        # A symbol table bug leaked the global statement from one

        # function to other nested functions in the same block.

        # This test verifies that a global statement in the first

        # function does not affect the second function.

        CODE = """def f():

    y = 1

    def g():

        global y

        return y

    def h():

        return y + 1

    return g, h



y = 9

g, h = f()

result9 = g()

result2 = h()

"""

        local_ns = {}

        global_ns = {}

        exec CODE in local_ns, global_ns

        self.assertEqual(2, global_ns["result2"])

        self.assertEqual(9, global_ns["result9"])





def test_main():

    run_unittest(ScopeTests)



if __name__ == '__main__':

    test_main()

