# pyflyby/test_autoimp.py

# License for THIS FILE ONLY: CC0 Public Domain Dedication
# http://creativecommons.org/publicdomain/zero/1.0/

from __future__ import (absolute_import, division, print_function,
                        with_statement)

import ast
import os
import pytest
from   shutil                   import rmtree
import sys
from   tempfile                 import mkdtemp
from   textwrap                 import dedent

from   six                      import PY2, PY3

from   pyflyby                  import (Filename, ImportDB, auto_eval,
                                        auto_import, find_missing_imports)
from   pyflyby._autoimp         import (LoadSymbolError, load_symbol,
                                        scan_for_import_issues)
from   pyflyby._idents          import DottedIdentifier
from   pyflyby._importstmt      import Import
from   pyflyby._flags           import CompilerFlags


@pytest.fixture
def tpp(request):
    """
    A temporary directory which is temporarily added to sys.path.
    """
    d = mkdtemp(prefix="pyflyby_test_autoimp_", suffix=".tmp")
    d = Filename(d).real
    def cleanup():
        # Unload temp modules.
        for name, module in sorted(sys.modules.items()):
            if (getattr(module, "__file__", None) or "").startswith(str(d)):
                del sys.modules[name]
        # Clean up sys.path.
        sys.path.remove(str(d))
        # Clean up directory on disk.
        rmtree(str(d))
    request.addfinalizer(cleanup)
    sys.path.append(str(d))
    return d


def writetext(filename, text, mode='w'):
    text = dedent(text)
    filename = Filename(filename)
    with open(str(filename), mode) as f:
        f.write(text)
    return filename


def _dilist2strlist(arg):
    assert type(arg) is list
    assert all(type(x) is DottedIdentifier for x in arg)
    return list(map(str, arg))


def test_find_missing_imports_basic_1():
    result   = find_missing_imports("os.path.join", namespaces=[{}])
    result   = _dilist2strlist(result)
    expected = ["os.path.join"]
    assert expected == result


def test_find_missing_imports_in_namespace_1():
    result   = find_missing_imports("os.path.join", namespaces=[{"os":os}])
    result   = _dilist2strlist(result)
    expected = []
    assert expected == result


def test_find_missing_imports_builtins_1():
    result   = find_missing_imports("os, sys, eval", [{"os": os}])
    result   = _dilist2strlist(result)
    expected = ['sys']
    assert expected == result


def test_find_missing_imports_undefined_1():
    result   = find_missing_imports("numpy.arange(x) + arange(y)", [{"y": 3}])
    result   = _dilist2strlist(result)
    expected = ['arange', 'numpy.arange', 'x']
    assert expected == result


def test_find_missing_imports_in_scope_1():
    result   = find_missing_imports("import numpy; numpy.arange(x) + arange(x)", [{}])
    result   = _dilist2strlist(result)
    expected = ['arange', 'x']
    assert expected == result


def test_find_missing_imports_in_scope_2():
    result   = find_missing_imports("from numpy import pi; numpy.pi + pi + x", [{}])
    result   = _dilist2strlist(result)
    expected = ['numpy.pi', 'x']
    assert expected == result


def test_find_missing_imports_in_scope_3():
    result   = find_missing_imports("for x in range(3): print(numpy.arange(x))", [{}])
    result   = _dilist2strlist(result)
    expected = ['numpy.arange']
    assert expected == result


def test_find_missing_imports_in_scope_funcall_1():
    result   = find_missing_imports("foo1 = func(); foo1.bar + foo2.bar", [{}])
    result   = _dilist2strlist(result)
    expected = ['foo2.bar', 'func']
    assert expected == result


def test_find_missing_imports_in_scope_assign_attr_1():
    result   = find_missing_imports("a.b.y = 1; a.b.x, a.b.y, a.b.z", [{}])
    result   = _dilist2strlist(result)
    expected = ['a.b.x', 'a.b.z']
    assert expected == result


def test_find_missing_imports_lambda_1():
    result   = find_missing_imports("(lambda x: x*x)(7)", [{}])
    result   = _dilist2strlist(result)
    expected = []
    assert expected == result


def test_find_missing_imports_lambda_2():
    result   = find_missing_imports("(lambda x: x*x)(7) + x", [{}])
    result   = _dilist2strlist(result)
    expected = ['x']
    assert expected == result


def test_find_missing_imports_lambda_3():
    result   = find_missing_imports("(lambda *a,**k: (a, k))(7, x=1)", [{}])
    result   = _dilist2strlist(result)
    expected = []
    assert expected == result


def test_find_missing_imports_list_comprehension_1():
    result   = find_missing_imports("[x+y+z for x,y in [(1,2)]], y", [{}])
    result   = _dilist2strlist(result)
    if PY2:
        expected = ['z']
    else:
        expected = ['y', 'z']
    assert expected == result


def test_find_missing_imports_list_comprehension_nested_tuple_1():
    result   = find_missing_imports("[w+x+y+z for x,(y,z) in []]", [{}])
    result   = _dilist2strlist(result)
    expected = ['w']
    assert expected == result


def test_find_missing_imports_list_comprehension_nested_tuple_2():
    result   = find_missing_imports(
        "[a+A+b+B+c+C+d+D+e+E+f+F+g+G for a,((b,c),d,[e,f,(g,)]) in []]", [{}])
    result   = _dilist2strlist(result)
    expected = ['A','B','C','D','E','F','G']
    assert expected == result


def test_find_missing_imports_generator_expression_1():
    result   = find_missing_imports("(x+y+z for x,y in [(1,2)]), y", [{}])
    result   = _dilist2strlist(result)
    expected = ['y', 'z']
    assert expected == result


def test_find_missing_imports_qualified_1():
    result   = find_missing_imports("( ( a . b ) . x ) . y + ( c + d ) . x . y", [{}])
    result   = _dilist2strlist(result)
    expected = ['a.b.x.y', 'c', 'd']
    assert expected == result


def test_find_missing_imports_ast_1():
    node = ast.parse("import numpy; numpy.arange(x) + arange(x)")
    result   = find_missing_imports(node, [{}])
    result   = _dilist2strlist(result)
    expected = ['arange', 'x']
    assert expected == result


def test_find_missing_imports_print_function_1():
    node = ast.parse(
        "from __future__ import print_function\n"
        "print (42, file=sys.stdout)\n"
    )
    result   = find_missing_imports(node, [{}])
    result   = _dilist2strlist(result)
    expected = ['sys.stdout']
    assert expected == result


def test_find_missing_imports_assignment_1():
    code = dedent("""
        from __future__ import print_function

        def f():
            x = 1
            print(x, y, z)
            y = 2
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['y', 'z']
    assert expected == result


def test_find_missing_imports_function_body_1():
    code = dedent("""
        x1 = 1
        def func59399065():
            return x1 + x2 + x3
        x3 = 3
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['x2']
    assert expected == result


def test_find_missing_imports_function_paramlist_1():
    code = dedent("""
        X1 = 1
        def func85025862(x1=X1, x2=X2, x3=X3):
            return x1 + x2 + x3
        X3 = 3
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['X2', 'X3']
    assert expected == result


def test_find_missing_imports_function_paramlist_2():
    code = dedent("""
        from somewhere import given, data

        @given(data())
        def blargh(data):
            pass
    """)
    result  = find_missing_imports(code, [{}])
    result  = _dilist2strlist(result)
    expected = []
    assert expected == result

def test_find_missing_imports_function_defaults_1():
    code = dedent("""
        e = 1
        def func32466773(a=b, b=c, c=a, d=d, e=e, f=1):
            pass
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['a', 'b', 'c', 'd']
    assert expected == result


def test_find_missing_imports_function_defaults_kwargs_1():
    code = dedent("""
        def func16049151(x=args, y=kwargs, z=y, *args, **kwargs):
            pass
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['args', 'kwargs', 'y']
    assert expected == result
    
@pytest.mark.skipif(
    PY2,
    reason="Python 3-only syntax.")
def test_find_missing_imports_kwarg_annotate():
    """
    pfb issue 162
    """
    code = dedent("""
        def func_pfb162(args:Dict, **kwargs:Any):
            pass
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['Any', 'Dict']
    assert expected == result


def test_find_missing_imports_function_defaults_kwargs_2():
    code = dedent("""
        args = 1
        kwargs = 2
        def func69790319(x=args, y=kwargs, z=y, *args, **kwargs):
            pass
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['y']
    assert expected == result


def test_find_missing_imports_function_paramlist_local_1():
    code = dedent("""
        x1 = 1
        x2 = 2
        def func77361554(x1, x3, x4):
            pass
        x4 = 4
        x1, x2, x3, x4, x5
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['x3', 'x5']
    assert expected == result


def test_find_missing_imports_function_paramlist_selfref_1():
    code = dedent("""
        f1 = 'x'
        def f2(g1=f1, g2=f2, g3=f3):
            return (g1, g2, g3)
        f3 = 'x'
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['f2', 'f3']
    assert expected == result


def test_find_missing_imports_function_paramlist_lambda_1():
    code = dedent("""
        X1 = 1
        def func85025862(x1=lambda: 1/X1, x2=lambda: 1/X2, x3=lambda: 1/X3):
            return x1() + x2() + x3()
        X3 = 3
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['X2']
    assert expected == result


def test_find_missing_imports_decorator_1():
    code = dedent("""
        deco1 = 'x'
        @deco1
        @deco2
        @deco3
        def func33144383():
            pass
        deco3 = 'x'
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['deco2', 'deco3']
    assert expected == result


def test_find_missing_imports_decorator_selfref_1():
    code = dedent("""
        deco = 'x'
        func1 = 'x'
        @deco(func1, func2, func3)
        def func2():
            pass
        func3 = 'x'
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['func2', 'func3']
    assert expected == result


def test_find_missing_imports_decorator_paramlist_1():
    code = dedent("""
        p2 = 2
        def deco(*args): pass
        @deco(p1, p2, p3)
        def foo74632516():
            pass
        p3 = 3
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['p1', 'p3']
    assert expected == result


def test_find_missing_imports_setattr_1():
    code = dedent("""
        aa = 1
        aa.xx.yy = 1
        bb.xx.yy = 1
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    # For now we intentionally don't auto-import 'bb' because that's more
    # likely to be a mistake.
    expected = []
    assert expected == result


def test_find_missing_imports_delattr_1():
    code = dedent("""
        foo1 = 1
        del foo1.bar, foo2.bar
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['foo2']
    assert expected == result


def test_find_missing_imports_delitem_lhs_1():
    code = dedent("""
        foo1 = 1
        del foo1[123], foo2[123]
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['foo2']
    assert expected == result


def test_find_missing_imports_delitem_rhs_1():
    code = dedent("""
        foo1 = 1
        bar1 = 1
        del foo1[bar1], foo1[bar2]
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['bar2']
    assert expected == result

# TODO: unit tests for local, nonlocal


def test_find_missing_imports_classdef_1():
    code = dedent("""
        class Mahopac:
            pass
        class Gleneida(Mahopac):
            pass
        Mahopac, Carmel, Gleneida
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['Carmel']
    assert expected == result


def test_find_missing_imports_class_base_1():
    code = dedent("""
        Mill = object
        class Mohansic(Crom, Mill):
            pass
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['Crom']
    assert expected == result


def test_find_missing_imports_class_name_2():
    code = dedent(
        """
        class Decimal:
            def foo(self):

                Decimal.x = 1
    """
    )
    result = find_missing_imports(code, [{}])
    result = _dilist2strlist(result)
    expected = []
    assert expected == result


@pytest.mark.xfail
def test_find_missing_import_xfail_after_pr_152():
    code = dedent(
        """
        class MyClass(object):
            Outer = MyClass
    """
    )
    result = find_missing_imports(code, [{}])
    result = _dilist2strlist(result)
    expected = ["MyClass"]
    assert result == expected


def test_method_reference_current_class():
    """
    A method can reference the current class

    But only if this is a toplevel class, nesting won't work in Python.
    """
    code = dedent(
        """
       class Decimal:
           def foo(self):
               Decimal.x = 1
               Float.y=1

           class Real:

               def foo():
                   Real.r = 1
   """
    )
    missing, unused = scan_for_import_issues(code, [{}])
    # result = _dilist2strlist(result)
    assert missing == [
        (5, DottedIdentifier("Float.y")),
        (10, DottedIdentifier("Real.r")),
    ]
    assert unused == []



def test_find_missing_imports_class_name_1():
    code = dedent(
        """
        class Corinne(object):
            pass
        class Bobtail(object):
            class Chippewa(object):
                Bobtail
            Rockton = Passall, Corinne, Chippewa
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['Bobtail', 'Passall']
    assert expected == result



def test_find_missing_imports_class_members_1():
    code = dedent("""
        class Kenosha(object):
            x = 3
            z = x, y
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['y']
    assert expected == result


def test_find_missing_imports_class_member_vs_function_1():
    code = dedent("""
        class Sidney(object):
            x = 3
            def barracuda(self):
                return x, y
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['x', 'y']
    assert expected == result


def test_find_missing_imports_class_member_vs_function_2():
    code = dedent("""
        class Wayne: pass
        class Connaught(object):
            class Windsor: pass
            def Mercury(self):
                return Wayne, Connaught, Windsor
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['Windsor']
    assert expected == result


def test_find_missing_imports_class_member_vs_lambda_1():
    code = dedent("""
        x1 = 1
        class Salsa(object):
            x2 = 2
            x3 = 3
            y = [lambda y3=x3, y4=x4: x1 + x2 + x5 + x6]
        x4 = 4
        x5 = 5
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['x2', 'x4', 'x6']
    assert expected == result


def test_find_missing_imports_class_member_vs_paramlist_1():
    code = dedent("""
        class Drake:
            duck2 = 2
            def quack(self, mallard1=duck1, mallard2=duck2, mallard3=duck3):
                pass
            duck3 = 3
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['duck1', 'duck3']
    assert expected == result


def test_find_missing_imports_class_member_vs_paramlist_lambda_1():
    code = dedent("""
        class Breakfast:
            def corn1(self):
                pass
            def cereal(self, maize1=lambda: corn1, maize2=lambda: corn2,
                             maize3=lambda: corn3):
                return (maize1(), maize2())
            def corn2(self):
                pass
        def corn3(self):
            pass
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['corn1', 'corn2']
    assert expected == result


def test_find_missing_imports_class_member_vs_paramlist_local_1():
    code = dedent("""
        class Legume:
            x1 = 1
            x2 = 2
            def func13585710(x1, x3, x4):
                pass
            x4 = 4
            y = x1, x2, x3, x4, x5
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['x3', 'x5']
    assert expected == result


def test_find_missing_imports_class_member_vs_decorator_1():
    code = dedent("""
        def deco(): 1/0
        class Cat:
            def panther1(self):
                pass
            @deco(panther1, panther2, panther3)
            def growl(self):
                pass
            def panther2(self):
                pass
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['panther2', 'panther3']
    assert expected == result


def test_find_missing_imports_inner_class_method_1():
    code = dedent("""
        class Sand(object):
            Dirt = 100
            class Silicon:
                def f(self):
                    return Sand, Dirt, Silicon, Glass
        class Glass:
            pass
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['Dirt', 'Silicon']
    assert expected == result


def test_find_missing_imports_inner_class_attribute_1():
    code = dedent("""
        x = 100
        class Axel(object):
            a = 100
            class Beth:
                b = x + a
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['a']
    assert expected == result


def test_find_missing_imports_class_member_function_ref_1():
    code = dedent("""
        class Niska(object):
            def f1(self): pass
            g = f1, f2
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['f2']
    assert expected == result


def test_find_missing_imports_class_member_generator_expression_1():
    # Verify that variables leak out of list comprehensions but not out of
    # generator expressions in Python 2.
    # Verify that both can see members of the same ClassDef.
    code = dedent("""
        class Caleb(object):
            x = []
            g1 = (1 for y1 in x)
            g2 = [1 for y2 in x]
            h = [y1, y2]
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    if PY2:
        expected = ['y1']
    else:
        expected = ['y1', 'y2']
    assert expected == result


def test_find_missing_imports_latedef_def_1():
    code = dedent("""
        def marble(x):
            return x + y + z
        z = 100
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['y']
    assert expected == result


def test_find_missing_imports_latedef_lambda_1():
    code = dedent("""
        granite = lambda x: x + y + z
        z = 100
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['y']
    assert expected == result


def test_find_missing_imports_latedef_def_def_1():
    code = dedent("""
        def twodot():
            return sterling() + haymaker() + cannon() + twodot()
        def haymaker():
            return 100
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['cannon', 'sterling']
    assert expected == result


def test_find_missing_imports_latedef_innerdef_1():
    code = dedent("""
        def kichawan(w):
            def turkey(x):
                return v + w + x + y + z
            z = 100
        v = 200
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['y']
    assert expected == result


def test_find_missing_imports_latedef_innerdef_2():
    code = dedent("""
        def maple(w):
            def drumgor(x):
                return v + w + x + y + z
            z = 100
        def springmere(w):
            def dorchester(x):
                return v + w + x + y + z
        v = 200
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['y', 'z']
    assert expected == result


def test_find_missing_imports_latedef_classdef_1():
    code = dedent("""
        a = 100
        class Granite:
            x = a, b
            def springs(self):
                x, y, z
        b = 100
        z = 100
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['b', 'x', 'y']
    assert expected == result


def test_find_missing_imports_latedef_func_class_func_1():
    code = dedent("""
        def Nellie():
            class Shelley:
                def Norman(self):
                    return Alfred, Sherry, Grover, Kirk
            Sherry = 100
        Kirk = 200
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['Alfred', 'Grover']
    assert expected == result


def test_find_missing_imports_latedef_if_1():
    code = dedent("""
        if 1:
            def cavalier():
                x, y
        if 1:
            x = 1
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['y']
    assert expected == result


def test_find_missing_imports_class_scope_comprehension_1():
    code = dedent("""
        class Plymouth:
            x = []
            z = list(1 for t in x+y)
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['y']
    assert expected == result


def test_find_missing_imports_global_1():
    code = dedent("""
        def func10663671():
            global x
            x = x + y
        x = 1
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['y']
    assert expected == result


def test_find_missing_imports_complex_1():
    if PY2:
        code = dedent("""
            x = 3+4j+5L+k+u'a'
        """)
    else:
        code = dedent("""
            x = 3+4j+5+k+u'a'
        """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['k']
    assert expected == result


def test_find_missing_imports_code_1():
    f = lambda: foo.bar(x) + baz(y) # noqa: F821
    result   = find_missing_imports(f.__code__, [{}])
    result   = _dilist2strlist(result)
    expected = ['baz', 'foo.bar', 'x', 'y']
    assert expected == result


def test_find_missing_imports_code_args_1():
    def f(x, y, *a, **k):
        return g(x, y, z, a, k) # noqa: F821
    result   = find_missing_imports(f.__code__, [{}])
    result   = _dilist2strlist(result)
    expected = ['g', 'z']
    assert expected == result


def test_find_missing_imports_code_use_after_import_1():
    def f():
        import foo
        foo.bar()
    result   = find_missing_imports(f.__code__, [{}])
    result   = _dilist2strlist(result)
    expected = []
    assert expected == result


def test_find_missing_imports_code_lambda_scope_1():
    f = lambda x: (lambda: x+y) # noqa: F821
    result   = find_missing_imports(f.__code__, [{}])
    result   = _dilist2strlist(result)
    expected = ['y']
    assert expected == result


def test_find_missing_imports_code_conditional_1():
    def f():
        y0 = x0          # noqa: F821
        if c:            # noqa: F821
            y1 = y0 + x1 # noqa: F821
        else:
            y2 = x2 + y0 # noqa: F821
        x3 + y0          # noqa: F821
        y1 + y2
    result   = find_missing_imports(f.__code__, [{}])
    result   = _dilist2strlist(result)
    expected = ['c', 'x0', 'x1', 'x2', 'x3']
    assert expected == result


def test_find_missing_imports_code_loop_1():
    def f():
        for i in range(10):
            if i > 0:
                use(x)     # noqa: F821
                use(y)     # noqa: F821
            else:
                x = "hello" # noqa: F841
    result   = find_missing_imports(f.__code__, [{}])
    result   = _dilist2strlist(result)
    expected = ['use', 'y']
    assert expected == result

@pytest.mark.skipif(
    sys.version_info < (3, 8),
    reason="Python 3.8+-only syntax.")
def test_find_missing_imports_positional_only_args_1():
    code = dedent("""
        def func(x, /, y):
            pass
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = []
    assert expected == result

@pytest.mark.skipif(
    PY2,
    reason="Python 3-only syntax.")
def test_find_missing_imports_keyword_only_args_1():
    code = dedent("""
        def func(*args, kwonly=b):
            a = kwonly
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['b']
    assert expected == result

@pytest.mark.skipif(
    PY2,
    reason="Python 3-only syntax.")
def test_find_missing_imports_keyword_only_args_2():
    code = dedent("""
        def func(*args, kwonly):
            a = kwonly
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = []
    assert expected == result


@pytest.mark.skipif(
    PY2,
    reason="Python 3-only syntax.")
def test_find_missing_imports_keyword_only_args_3():
    code = dedent("""
        def func(*args, kwonly, kwonly2=b):
            a = kwonly
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['b']
    assert expected == result

@pytest.mark.skipif(
    PY2,
    reason="Python 3-only syntax.")
def test_find_missing_imports_annotations_1():
    code = dedent("""
        def func(a: b) -> c:
            d = a
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['b', 'c']
    assert expected == result

@pytest.mark.skipif(
    PY2,
    reason="Python 3-only syntax.")
def test_find_missing_imports_annotations_2():
    code = dedent("""
    a: b = c
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['b', 'c']
    assert expected == result

@pytest.mark.skipif(
    PY2,
    reason="Python 3-only syntax.")
def test_find_missing_imports_star_assignments_1():
    code = dedent("""
    a, *b = c
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['c']
    assert expected == result

@pytest.mark.skipif(
    PY2,
    reason="Python 3-only syntax.")
def test_find_missing_imports_star_expression_1():
    code = dedent("""
    [a, *b]
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['a', 'b']
    assert expected == result


@pytest.mark.skipif(
    PY2,
    reason="Python 3-only syntax.")
def test_find_missing_imports_star_expression_2():
    code = dedent("""
    {a: 1, **b, **{c: 1}}
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['a', 'b', 'c']
    assert expected == result

@pytest.mark.skipif(
    PY2,
    reason="Python 3-only syntax.")
def test_find_missing_imports_star_expression_function_call_1():
    code = dedent("""
    f(a, *b, **c, d=e, **g)
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['a', 'b', 'c', 'e', 'f', 'g']
    assert expected == result

def test_find_missing_imports_star_expression_function_call_2():
    # Python 2-valid syntax
    code = dedent("""
    f(a, b=c, *d, **e)
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['a', 'c', 'd', 'e', 'f']
    assert expected == result

@pytest.mark.skipif(
    PY2,
    reason="Python 3-only syntax.")
def test_find_missing_imports_python_3_metaclass_1():
    code = dedent("""
    class Test(metaclass=TestMeta):
        pass
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['TestMeta']
    assert expected == result


@pytest.mark.skipif(
    PY2,
    reason="Python 3-only syntax.")
def test_find_missing_imports_f_string_1():
    code = dedent("""
    a = 1
    f'{a + 1} {b + 1}'
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['b']
    assert expected == result

@pytest.mark.skipif(
    PY2,
    reason="Python 3-only syntax.")
def test_find_missing_imports_f_string_2():
    code = dedent("""
    a = 1
    f'{a!s} {b!r}'
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['b']
    assert expected == result

@pytest.mark.skipif(
    PY2,
    reason="Python 3-only syntax.")
def test_find_missing_imports_f_string_3():
    # Recursive format spec
    code = dedent("""
    f'{a:{b}!s}'
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['a', 'b']
    assert expected == result

def test_find_missing_imports_bytes_1():
    code = dedent("""
        a = b'b'
    """)
    result = find_missing_imports(code, [{}])
    result = _dilist2strlist(result)
    expected = []
    assert expected == result

def test_find_missing_imports_true_false_none_1():
    # These nodes changed in Python 3, make sure they are handled correctly
    code = dedent("""
    (True, False, None)
    """)
    result = find_missing_imports(code, [{}])
    result = _dilist2strlist(result)
    expected = []
    assert expected == result


@pytest.mark.skipif(
    PY2,
    reason="Python 3-only syntax.")
def test_find_missing_imports_matmul_1():
    code = dedent("""
    a@b
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['a', 'b']
    assert expected == result

@pytest.mark.skipif(
    PY2,
    reason="Python 3-only syntax.")
def test_find_missing_imports_async_await_1():
    code = dedent("""
    async def f():
        async with a as b, c as d:
            async for i in e:
                g = await h()
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['a', 'c', 'e', 'h']
    assert expected == result

@pytest.mark.skipif(
    PY2,
    reason="Python 3-only syntax.")
def test_find_missing_imports_async_comprehension_1():
    code = dedent("""
    async def f():
        [i async for i in range(2)]
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = []
    assert expected == result


@pytest.mark.skipif(
    PY2,
    reason="Python 3-only syntax.")
def test_find_missing_imports_yield_from_1():
    code = dedent("""
    def f():
        yield from g()
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['g']
    assert expected == result


@pytest.mark.skipif(PY2, reason="Python 3-only syntax.")
def test_find_missing_imports_not_async_def():
    code = dedent(
        """
    async def f():
        pass
    f()
    """
    )
    result = find_missing_imports(code, [{}])
    expected = []
    assert expected == result


def test_find_missing_imports_nested_with_1():
    # Handled differently in the ast in Python 2 and 3
    code = dedent("""
    with a as b, c as d:
        pass
    """)
    result   = find_missing_imports(code, [{}])
    result   = _dilist2strlist(result)
    expected = ['a', 'c']
    assert expected == result

def test_find_missing_imports_exception_1():
    code = dedent("""
    try:
        a = 1
    except:
        pass
    """)
    result = find_missing_imports(code, [{}])
    result = _dilist2strlist(result)
    expected = []
    assert expected == result

def test_find_missing_imports_exception_2():
    code = dedent("""
    try:
        a = 1
    except SomeException:
        pass
    """)
    result = find_missing_imports(code, [{}])
    result = _dilist2strlist(result)
    expected = ['SomeException']
    assert expected == result

def test_find_missing_imports_exception_3():
    code = dedent("""
    try:
        a = 1
    except SomeException as e:
        pass
    """)
    result = find_missing_imports(code, [{}])
    result = _dilist2strlist(result)
    expected = ['SomeException']
    assert expected == result


def test_find_missing_imports_tuple_ellipsis_type_1():
    code = dedent("""
    tuple[Foo, ..., Bar]
    """)
    result = find_missing_imports(code, [{}])
    result = _dilist2strlist(result)
    expected = ['Bar', 'Foo']
    assert expected == result


# Only Python 3.8 includes type comments in the ast, so we only support this
# there (see issue #31).
@pytest.mark.skipif(
    sys.version_info < (3, 8),
    reason="Python 3.8+-only support.")
def test_scan_for_import_issues_type_comment_1():
    code = dedent("""
    from typing import Sequence
    def foo(strings  # type: Sequence[str]
            ):
        pass
    """)
    missing, unused = scan_for_import_issues(code)
    assert unused == []
    assert missing == []


# Only Python 3.8 includes type comments in the ast, so we only support this
# there (see issue #31, 171, 174).
@pytest.mark.skipif(
    sys.version_info < (3, 8),
    reason="Python 3.8+-only support.")
def test_scan_for_import_issues_type_comment_2():
    code = dedent("""
    from typing import Sequence
    def foo(strings):
        # type: (Sequence[str]) -> None
        pass
    """)
    missing, unused = scan_for_import_issues(code)
    assert unused == []
    assert missing == []


@pytest.mark.skipif(
    sys.version_info < (3, 8),
    reason="Python 3.8+-only support.")
def test_scan_for_import_issues_type_comment_3():
    code = dedent("""
    def foo(strings):
        # type: (Sequence[str]) -> None
        pass
    """)
    missing, unused = scan_for_import_issues(code)
    assert unused == []
    assert missing == [(1, DottedIdentifier('Sequence'))]


@pytest.mark.skipif(
    sys.version_info < (3, 8),
    reason="Python 3.8+-only support.")
def test_scan_for_import_issues_type_comment_4():
    code = dedent("""
    from typing import Sequence, Tuple
    def foo(strings):
        # type: (Sequence[str]) -> None
        pass
    """)
    missing, unused = scan_for_import_issues(code)
    assert unused == [(2, Import('from typing import Tuple'))]
    assert missing == []

# Python 3.8 uses the correct line number for multiline strings (the first
# line), making _annotate_ast_startpos irrelevant. Otherwise, the logic for
# getting this right is too hard. See issue #12.
@pytest.mark.skipif(
    sys.version_info < (3, 8),
    reason="Python 3.8+-only support.")
def test_scan_for_import_issues_multiline_string_1():
    code = dedent('''
    x = (
        """
        a
        """
        # blah
        "z"
    )
    ''')
    missing, unused = scan_for_import_issues(code)
    assert unused == []
    assert missing == []

def test_scan_for_import_issues_dictcomp_missing_1():
    code = dedent("""
        y1 = y2 = 1234
        {(x1,y1,z1): (x2,y2,z2) for x1,x2 in []}
    """)
    missing, unused = scan_for_import_issues(code)
    assert unused == []
    assert missing == [(3, DottedIdentifier('z1')), (3, DottedIdentifier('z2'))]


def test_scan_for_import_issues_dictcomp_unused_1():
    code = dedent("""
        import x1, x2, x3
        {123:x3 for x1,x2 in []}
    """)
    missing, unused = scan_for_import_issues(code)
    assert missing == []
    assert unused == [(2, Import('import x1')), (2, Import('import x2'))]


def test_scan_for_import_issues_setcomp_missing_1():
    code = dedent("""
        y1 = 1234
        {(x1,y1,z1) for x1,x2 in []}
    """)
    missing, unused = scan_for_import_issues(code)
    assert unused == []
    assert missing == [(3, DottedIdentifier('z1'))]


def test_scan_for_import_issues_setcomp_unused_1():
    code = dedent("""
        import x1, x2
        {x2 for x1 in []}
    """)
    missing, unused = scan_for_import_issues(code)
    assert missing == []
    assert unused == [(2, Import('import x1'))]


def test_scan_for_import_issues_class_subclass_imported_class_1():
    code = dedent("""
        from m1 import C1
        class C1(C1): pass
    """)
    missing, unused = scan_for_import_issues(code)
    assert missing == []
    assert unused == []


# This is now an XPASS, let's remove the xfail to see if it fails only on some CI version.
# # This is currently buggy.
# # The problem is that we postpone the check for C1.base = m1.C1, and by the
# # time we check it, we've already replaced the thing in the scope.
# @pytest.mark.xfail
def test_scan_for_import_issues_class_subclass_imported_class_in_func_1():
    code = dedent("""
        def f1():
            from m1 import C1
            class C1(C1): pass
    """)
    missing, unused = scan_for_import_issues(code)
    assert missing == []
    assert unused == []


def test_scan_for_import_issues_use_then_del_in_func_1():
    code = dedent("""
        def f1():
            x1 = 1
            x1
            del x1
    """)
    missing, unused = scan_for_import_issues(code)
    assert missing == []
    assert unused == []


def test_scan_for_import_issues_use_then_del_in_func_2():
    code = dedent("""
        def f1():
            x1 = 123
            print(x1); print(x2)
            del x1
    """)
    missing, unused = scan_for_import_issues(code)
    assert missing == [(4, DottedIdentifier('x2'))]
    assert unused == []


def test_scan_for_import_issues_del_in_func_then_use_1():
    code = dedent("""
        def f1():
            x1 = 123
            del x1
            print(x1); print(x2)
    """)
    missing, unused = scan_for_import_issues(code)
    assert missing == [(5, DottedIdentifier('x1')), (5, DottedIdentifier('x2'))]
    assert unused == []


def test_scan_for_import_issues_brace_identifiers_1():
    code = dedent("""
        import x1, x2, x3
        def f():
            '''{x1} {x3}'''
    """)
    missing, unused = scan_for_import_issues(code, parse_docstrings=True)
    assert missing == []
    assert unused == [(2, Import('import x2'))]


def test_scan_for_import_issues_brace_identifiers_bad_1():
    code = dedent("""
        import x1, x2, x3
        def f():
            '''{x1} {x3} {if}'''
    """)
    missing, unused = scan_for_import_issues(code, parse_docstrings=True)
    assert missing == []
    assert unused == [(2, Import('import x2'))]


def test_scan_for_import_issues_star_import_1():
    code = dedent("""
        import x1, y1
        x1, x2
        from m2 import *
        x3
    """)
    missing, unused = scan_for_import_issues(code, parse_docstrings=True)
    assert missing == [(3, DottedIdentifier('x2'))]
    assert unused == [(2, Import('import y1'))]


def test_scan_for_import_issues_star_import_deferred_1():
    code = dedent("""
        import x1, y1
        def f1():
            x1, x2
        from m2 import *
        def f1():
            x3
    """)
    missing, unused = scan_for_import_issues(code, parse_docstrings=True)
    assert missing == []
    assert unused == [(2, Import('import y1'))]


def test_scan_for_import_issues_star_import_local_1():
    code = dedent("""
        import x1, y1
        def f1():
            from m2 import *
            x2
        def f2():
            x3
        x1, x4
    """)
    missing, unused = scan_for_import_issues(code, parse_docstrings=True)
    assert missing == [(7, DottedIdentifier('x3')), (8, DottedIdentifier('x4'))]
    assert unused == [(2, Import('import y1'))]


def test_scan_for_import_issues_comprehension_subscript_1():
    code = dedent("""
        x = []
        [123 for x[0] in []]
    """)
    missing, unused = scan_for_import_issues(code, parse_docstrings=True)
    assert missing == []
    assert unused == []


def test_scan_for_import_issues_comprehension_subscript_missing_1():
    code = dedent("""
        [123 for x[0] in []]
    """)
    missing, unused = scan_for_import_issues(code, parse_docstrings=True)
    assert missing == [(2, DottedIdentifier('x'))]
    assert unused == []


def test_scan_for_import_issues_comprehension_subscript_complex_1():
    code = dedent("""
        dd = []
        [(aa,bb) for (bb, cc[0], dd[0]) in []]
    """)
    missing, unused = scan_for_import_issues(code, parse_docstrings=True)
    assert missing == [(3, DottedIdentifier('aa')),
                       (3, DottedIdentifier('cc'))]
    assert unused == []


def test_scan_for_import_issues_comprehension_attribute_1():
    code = dedent("""
        xx = []
        [123 for xx.yy in []]
    """)
    missing, unused = scan_for_import_issues(code, parse_docstrings=True)
    assert missing == []
    assert unused == []


@pytest.mark.xfail
def test_scan_for_import_issues_comprehension_attribute_missing_1():
    code = dedent("""
        [123 for xx.yy in []]
    """)
    missing, unused = scan_for_import_issues(code, parse_docstrings=True)
    assert missing == [(3, DottedIdentifier('xx'))]
    assert unused == []

# this is now an XPASS, check that it's passing everywhere in CI
# @pytest.mark.xfail
def test_scan_for_import_issues_comprehension_attribute_complex_1():
    code = dedent("""
        dd = []
        [(aa,bb) for (bb, cc.cx, dd.dx) in []]
    """)
    missing, unused = scan_for_import_issues(code, parse_docstrings=True)
    assert missing == [(3, DottedIdentifier('aa')),
                       (3, DottedIdentifier('cc.cx'))]
    assert unused == []


def test_scan_for_import_issues_comprehension_attribute_subscript_1():
    code = dedent("""
        xx = []
        [123 for xx.yy[0] in []]
    """)
    missing, unused = scan_for_import_issues(code, parse_docstrings=True)
    assert missing == []
    assert unused == []


def test_scan_for_import_issues_comprehension_attribute_subscript_missing_1():
    code = dedent("""
        [123 for xx.yy[0] in []]
    """)
    missing, unused = scan_for_import_issues(code, parse_docstrings=True)
    assert missing == [(2, DottedIdentifier('xx.yy'))]
    assert unused == []


def test_scan_for_import_issues_comprehension_subscript_attribute_1():
    code = dedent("""
        xx = []
        [123 for xx[0].yy in []]
    """)
    missing, unused = scan_for_import_issues(code, parse_docstrings=True)
    assert missing == []
    assert unused == []


def test_scan_for_import_issues_comprehension_subscript_attribute_missing_1():
    code = dedent("""
        [123 for xx[0].yy in []]
    """)
    missing, unused = scan_for_import_issues(code, parse_docstrings=True)
    assert missing == [(2, DottedIdentifier('xx'))]
    assert unused == []


def test_scan_for_import_issues_generator_comprehension_subscript_attribute_1():
    code = dedent("""
        xx = []
        (123 for xx[0].yy in [])
    """)
    missing, unused = scan_for_import_issues(code, parse_docstrings=True)
    assert missing == []
    assert unused == []


def test_scan_for_import_issues_set_comprehension_subscript_attribute_1():
    code = dedent("""
        xx = []
        {123 for xx[0].yy in []}
    """)
    missing, unused = scan_for_import_issues(code, parse_docstrings=True)
    assert missing == []
    assert unused == []


def test_scan_for_import_issues_dict_comprehension_subscript_attribute_1():
    code = dedent("""
        xx = []
        {123:456 for xx[0].yy in []}
    """)
    missing, unused = scan_for_import_issues(code, parse_docstrings=True)
    assert missing == []
    assert unused == []


def test_scan_for_import_issues_setattr_1():
    code = dedent("""
        import aa, cc
        aa.xx.yy = 1
        bb.xx.yy = 1
    """)
    missing, unused = scan_for_import_issues(code)
    assert missing == [(4, DottedIdentifier('bb.xx.yy'))]
    # 'cc' should be marked as an unused-import, but 'aa' should be considered
    # used.  (This was buggy before 201907.)
    assert unused == [(2, Import('import cc'))]


def test_scan_for_import_issues_setattr_in_func_1():
    code = dedent("""
        import aa, cc
        def f():
            aa.xx.yy = 1
            bb.xx.yy = 1
    """)
    missing, unused = scan_for_import_issues(code)
    assert missing == [(5, DottedIdentifier('bb.xx.yy'))]
    # 'cc' should be marked as an unused-import, but 'aa' should be considered
    # used.  (This was buggy before 201907.)
    assert unused == [(2, Import('import cc'))]

def test_setattr_is_not_unused():
    code = dedent("""
        from a import b
        def f():
            b.xx.yy = 1

    """)
    missing, unused = scan_for_import_issues(code)
    # b should be considered used
    assert missing == []
    assert unused == []


def test_all_exports_1():
    code = dedent("""
        from os import path, walk, read
        __all__ = ['path', 'rmdir', 'walk']
    """)
    missing, unused = scan_for_import_issues(code)
    # path and walk should not be unused
    assert missing == [(3, DottedIdentifier('rmdir'))]
    assert unused == [(2, Import('from os import read'))]


def test_all_exports_2():
    code = dedent("""
        from __future__ import absolute_import, division

        __all__ = ["defaultdict", "rmdir"]


        def defaultdict():
            pass
    """)
    missing, unused = scan_for_import_issues(code)
    assert missing == [(4, DottedIdentifier('rmdir'))]
    assert unused == []



def test_load_symbol_1():
    assert load_symbol("os.path.join", {"os": os}) is os.path.join


def test_load_symbol_2():
    assert load_symbol("os.path.join.__name__", {"os": os}) == "join"


def test_load_symbol_missing_1():
    with pytest.raises(LoadSymbolError):
        load_symbol("os.path.join.asdfasdf", {"os": os})


def test_load_symbol_missing_2():
    with pytest.raises(LoadSymbolError):
        load_symbol("os.path.join", {})


def load_symbol_eval_1():
    assert 'a/b' == load_symbol("os.path.join('a','b')", {"os": os})
    assert '/'   == load_symbol("os.path.join('a','b')[1]", {"os": os})
    assert 'A'   == load_symbol("os.path.join('a','b')[0].upper()", {"os": os})


def load_symbol_eval_2(capsys):
    assert '/' == load_symbol("(os.path.sep[0])", {}, autoimport=True,
                              allow_eval=True)
    out, _ = capsys.readouterr()
    expected = dedent("""
        [PYFLYBY] import os
    """).lstrip()
    assert expected == out


def load_symbol_no_eval_1():
    with pytest.raises(LoadSymbolError):
        load_symbol("os.path.join('a','b')", {"os": os})
    with pytest.raises(LoadSymbolError):
        load_symbol("os.path.join('a','b')[1]", {"os": os})
    with pytest.raises(LoadSymbolError):
        load_symbol("os.path.join('a','b')[0].upper", {"os": os})


def load_symbol_wrap_exc_1():
    class Foo89503828(object):
        def __getattr__(self, k):
            1/0
    ns = [{"foo": Foo89503828()}]
    try:
        load_symbol("foo.bar", ns)
    except LoadSymbolError as e:
        assert type(e.__cause__) == ZeroDivisionError
    else:
        assert False


def load_symbol_wrap_exc_eval_1():
    def foo31617859():
        1/0
    ns = [{"foo": foo31617859()}]
    try:
        load_symbol("foo()", ns, auto_eval=True)
    except LoadSymbolError as e:
        assert type(e.__cause__) == ZeroDivisionError
    else:
        assert False


def load_symbol_wrap_exc_eval_getattr_1():
    class Foo15356301(object):
        def __getattr__(self, k):
            1/0
    ns = [{"foo": Foo15356301()}]
    try:
        load_symbol("foo.bar", ns, auto_eval=True)
    except LoadSymbolError as e:
        assert type(e.__cause__) == ZeroDivisionError
    else:
        assert False


def test_auto_eval_1():
    result = auto_eval("b64decode('aGVsbG8=')")
    assert result == b'hello'


def test_auto_eval_locals_import_1():
    mylocals = {}
    result = auto_eval("b64decode('aGVsbG8=')", locals=mylocals)
    assert result == b'hello'
    assert mylocals["b64decode"] is __import__("base64").b64decode


def test_auto_eval_globals_import_1():
    myglobals = {}
    result = auto_eval("b64decode('aGVsbG8=')", globals=myglobals)
    assert result == b'hello'
    assert myglobals["b64decode"] is __import__("base64").b64decode


def test_auto_eval_custom_locals_1():
    result = auto_eval("b64decode('aGVsbG8=')",
                                   locals=dict(b64decode=lambda x: "blah"))
    assert result == 'blah'


def test_auto_eval_custom_globals_1():
    result = auto_eval("b64decode('aGVsbG8=')",
                                   globals=dict(b64decode=lambda x: "blah"))
    assert result == 'blah'


def test_auto_eval_exec_1():
    mylocals = dict(x=[])
    auto_eval("if True: x.append(b64decode('aGVsbG8='))",
              locals=mylocals)
    assert mylocals['x'] == [b'hello']
    assert mylocals["b64decode"] is __import__("base64").b64decode


def test_auto_eval_no_auto_flags_ps_flagps_1(capsys):
    if PY2:
        auto_eval("print 3.00", flags=None, auto_flags=False)
        out, _ = capsys.readouterr()
        assert out == "3.0\n"
    else:
        auto_eval("print(3.00)", flags=CompilerFlags.from_int(0), auto_flags=False)
        out, _ = capsys.readouterr()
        assert out == "3.0\n"

def test_auto_eval_no_auto_flags_ps_flag_pf1():
    with pytest.raises(SyntaxError):
        auto_eval("print 3.00", flags="print_function", auto_flags=False)


@pytest.mark.skipif(
    PY3,
    reason="print function is not invalid syntax in Python 3.")
def test_auto_eval_no_auto_flags_pf_flagps_1():
    with pytest.raises(SyntaxError):
        auto_eval("print(3.00, file=sys.stdout)", flags=None, auto_flags=False)


def test_auto_eval_no_auto_flags_pf_flag_pf1(capsys):
    auto_eval("print(3.00, file=sys.stdout)",
              flags="print_function", auto_flags=False)
    out, _ = capsys.readouterr()
    assert out == "[PYFLYBY] import sys\n3.0\n"


def test_auto_eval_auto_flags_ps_flagps_1(capsys):
    if PY2:
        auto_eval("print 3.00", flags=CompilerFlags.from_int(0), auto_flags=True)
        out, _ = capsys.readouterr()
        assert out == "3.0\n"
    else:
        with pytest.raises(SyntaxError):
            auto_eval("print 3.00", flags=CompilerFlags.from_int(0), auto_flags=True)

@pytest.mark.skipif(
    PY3,
    reason="print not as a function cannot be valid syntax in Python 3.")
def test_auto_eval_auto_flags_ps_flag_pf1(capsys):
    auto_eval("print 3.00", flags=CompilerFlags("print_function"), auto_flags=True)
    out, _ = capsys.readouterr()
    assert out == "3.0\n"


def test_auto_eval_auto_flags_pf_flagps_1(capsys):
    auto_eval("print(3.00, file=sys.stdout)", flags=CompilerFlags.from_int(0), auto_flags=True)
    out, _ = capsys.readouterr()
    assert out == "[PYFLYBY] import sys\n3.0\n"


def test_auto_eval_auto_flags_pf_flag_pf1(capsys):
    auto_eval("print(3.00, file=sys.stdout)",
              flags=CompilerFlags("print_function"), auto_flags=True)
    out, _ = capsys.readouterr()
    assert out == "[PYFLYBY] import sys\n3.0\n"


def test_auto_eval_proxy_module_1(tpp, capsys):
    os.mkdir("%s/tornado83183065"%tpp)
    writetext(tpp/"tornado83183065/__init__.py", """
        import sys
        twister = 54170888
        class P:
            def __getattr__(self, K):
                k = K.lower()
                if k == K:
                    raise AttributeError
                else:
                    return getattr(self, k)
        p = P()
        p.__dict__ = globals()
        p._m = sys.modules[__name__]
        sys.modules[__name__] = p
    """)
    writetext(tpp/"tornado83183065/hurricane.py", """
        cyclone = 79943637
    """)
    # Verify that we can auto-import a sub-module of a proxy module.
    result = auto_eval("tornado83183065.hurricane.cyclone")
    out, _ = capsys.readouterr()
    expected = dedent("""
        [PYFLYBY] import tornado83183065
        [PYFLYBY] import tornado83183065.hurricane
    """).lstrip()
    assert out == expected
    assert result == 79943637
    # Verify that the proxy module can do its magic stuff.
    result = auto_eval("tornado83183065.TWISTER")
    out, _ = capsys.readouterr()
    assert out == "[PYFLYBY] import tornado83183065\n"
    assert result == 54170888
    # Verify that the proxy module can do its magic stuff with a submodule
    # that's already imported.
    result = auto_eval("tornado83183065.HURRICANE.cyclone")
    out, _ = capsys.readouterr()
    assert out == "[PYFLYBY] import tornado83183065\n"
    assert result == 79943637


def test_auto_import_1(capsys):
    auto_import("sys.asdfasdf", [{}])
    out, _ = capsys.readouterr()
    expected = dedent("""
        [PYFLYBY] import sys
    """).lstrip()
    assert expected == out


def test_auto_import_multi_1(capsys):
    auto_import("sys.asdfasdf + os.asdfasdf", [{}])
    out, _ = capsys.readouterr()
    expected = dedent("""
        [PYFLYBY] import os
        [PYFLYBY] import sys
    """).lstrip()
    assert expected == out


def test_auto_import_nothing_1(capsys):
    auto_import("sys.asdfasdf", [{"sys":sys}])
    out, _ = capsys.readouterr()
    assert out == ""


def test_auto_import_some_1(capsys):
    auto_import("sys.asdfasdf + os.asdfasdf", [{"sys":sys}])
    out, _ = capsys.readouterr()
    expected = dedent("""
        [PYFLYBY] import os
    """).lstrip()
    assert expected == out


def test_auto_import_custom_1(tpp, capsys):
    writetext(tpp/"trampoline77069527.py", """
        print('hello  world')
    """)
    auto_import("trampoline77069527.asdfasdf", [{}])
    out, _ = capsys.readouterr()
    expected = dedent("""
        [PYFLYBY] import trampoline77069527
        hello  world
    """).lstrip()
    assert expected == out


def test_auto_import_custom_in_pkg_1(tpp, capsys):
    os.mkdir(str(tpp/"truck56331367"))
    writetext(tpp/"truck56331367/__init__.py", "")
    writetext(tpp/"truck56331367/tractor.py", """
        print('hello  there')
    """)
    auto_import("truck56331367.tractor", [{}])
    out, _ = capsys.readouterr()
    expected = dedent("""
        [PYFLYBY] import truck56331367
        [PYFLYBY] import truck56331367.tractor
        hello  there
    """).lstrip()
    assert expected == out


def test_auto_import_unknown_1(capsys):
    # Verify that if we try to access something that doesn't appear to be a
    # module, we don't attempt to import it (or at least don't log any visible
    # errors for it).
    auto_import("electron91631346.asdfasdf", [{}])
    out, _ = capsys.readouterr()
    assert out == ""


def test_auto_import_unknown_but_in_db1(tpp, capsys):
    # Verify that if we try to access something that's in the known-imports
    # database, but it doesn't actually exist, we get a visible error for it.
    db = ImportDB('import photon70447198')
    auto_import("photon70447198.asdfasdf", [{}], db=db)
    out, _ = capsys.readouterr()
    if PY2:
        expected = dedent("""
            [PYFLYBY] import photon70447198
            [PYFLYBY] Error attempting to 'import photon70447198': ImportError: No module named photon70447198
            Traceback (most recent call last):
        """).lstrip()
    else:
        expected = dedent("""
            [PYFLYBY] import photon70447198
            [PYFLYBY] Error attempting to 'import photon70447198': ModuleNotFoundError: No module named 'photon70447198'
            Traceback (most recent call last):
        """).lstrip()

    assert out.startswith(expected)


def test_auto_import_fake_importerror_1(tpp, capsys):
    writetext(tpp/"proton24412521.py", """
        raise ImportError("No module named proton24412521")
    """)
    auto_import("proton24412521.asdfasdf", [{}])
    out, _ = capsys.readouterr()
    expected = dedent("""
        [PYFLYBY] import proton24412521
        [PYFLYBY] Error attempting to 'import proton24412521': ImportError: No module named proton24412521
        Traceback (most recent call last):
    """).lstrip()
    assert out.startswith(expected)


def test_auto_import_indirect_importerror_1(tpp, capsys):
    writetext(tpp/"neutron46291483.py", """
        import baryon96446873
    """)
    auto_import("neutron46291483.asdfasdf", [{}])
    out, _ = capsys.readouterr()
    if PY2:
        expected = dedent("""
            [PYFLYBY] import neutron46291483
            [PYFLYBY] Error attempting to 'import neutron46291483': ImportError: No module named baryon96446873
            Traceback (most recent call last):
        """).lstrip()
    else:
        expected = dedent("""
            [PYFLYBY] import neutron46291483
            [PYFLYBY] Error attempting to 'import neutron46291483': ModuleNotFoundError: No module named 'baryon96446873'
            Traceback (most recent call last):
        """).lstrip()

    assert out.startswith(expected)


def test_auto_import_nameerror_1(tpp, capsys):
    writetext(tpp/"lepton69688541.py", """
        foo
    """)
    auto_import("lepton69688541.asdfasdf", [{}])
    out, _ = capsys.readouterr()
    expected = dedent("""
        [PYFLYBY] import lepton69688541
        [PYFLYBY] Error attempting to 'import lepton69688541': NameError: name 'foo' is not defined
        Traceback (most recent call last):
    """).lstrip()
    assert out.startswith(expected)


def test_post_import_hook_incomplete_import():
    db = ImportDB('import glob')
    expected = Import('import glob')
    def test_hook(val):
        assert val == expected
    auto_import("glob.glob('*')", [{}], db=db, post_import_hook=test_hook)


def test_post_import_hook_alias_import():
    db = ImportDB('import numpy as np')
    expected = Import('import numpy as np')
    def test_hook(val):
        assert val == expected
    auto_import("np", [{}], db=db, post_import_hook=test_hook)


def test_post_import_hook_from_statement():
    db = ImportDB('from numpy import compat')
    expected = Import('from numpy import compat')
    def test_hook(val):
        assert val == expected
    auto_import("compat", [{}], db=db, post_import_hook=test_hook)


def test_post_import_hook_fullname():
    db = ImportDB('import numpy.compat')
    expected = Import('import numpy.compat')
    def test_hook(val):
        assert val == expected
    auto_import("numpy.compat", [{}], db=db, post_import_hook=test_hook)

@pytest.mark.skipif(
    PY2,
    reason="Python 3-only namespace package.")
def test_namespace_package(tpp, capsys):
    os.mkdir(str(tpp/'namespace_package'))
    auto_import("namespace_package", [{}])
    out, _ = capsys.readouterr()
    expected = dedent("""
        [PYFLYBY] import namespace_package
    """).lstrip()
    assert out.startswith(expected)
