# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt

"""Tests for coverage.py's arc measurement."""

from tests.coveragetest import CoverageTest

import coverage
from coverage import env
from coverage.files import abs_file


class SimpleArcTest(CoverageTest):
    """Tests for coverage.py's arc measurement."""

    def test_simple_sequence(self):
        self.check_coverage("""\
            a = 1
            b = 2
            """,
            arcz=".1 12 2.")
        self.check_coverage("""\
            a = 1

            b = 3
            """,
            arcz=".1 13 3.")
        self.check_coverage("""\

            a = 2
            b = 3

            c = 5
            """,
            arcz=".2 23 35 5-2")

    def test_function_def(self):
        self.check_coverage("""\
            def foo():
                a = 2

            foo()
            """,
            arcz=".1 .2 14 2. 4.")

    def test_if(self):
        self.check_coverage("""\
            a = 1
            if len([]) == 0:
                a = 3
            assert a == 3
            """,
            arcz=".1 12 23 24 34 4.", arcz_missing="24")
        self.check_coverage("""\
            a = 1
            if len([]) == 1:
                a = 3
            assert a == 1
            """,
            arcz=".1 12 23 24 34 4.", arcz_missing="23 34")

    def test_if_else(self):
        self.check_coverage("""\
            if len([]) == 0:
                a = 2
            else:
                a = 4
            assert a == 2
            """,
            arcz=".1 12 25 14 45 5.", arcz_missing="14 45")
        self.check_coverage("""\
            if len([]) == 1:
                a = 2
            else:
                a = 4
            assert a == 4
            """,
            arcz=".1 12 25 14 45 5.", arcz_missing="12 25")

    def test_compact_if(self):
        self.check_coverage("""\
            a = 1
            if len([]) == 0: a = 2
            assert a == 2
            """,
            arcz=".1 12 23 3.", arcz_missing="")
        self.check_coverage("""\
            def fn(x):
                if x % 2: return True
                return False
            a = fn(1)
            assert a == True
            """,
            arcz=".1 14 45 5.  .2 2. 23 3.", arcz_missing="23 3.")

    def test_multiline(self):
        self.check_coverage("""\
            a = (
                2 +
                3
                )
            b = \\
                6
            """,
            arcz=".1 15 5-2", arcz_missing="")

    def test_if_return(self):
        self.check_coverage("""\
            def if_ret(a):
                if a:
                    return 3
                b = 4
                return 5
            x = if_ret(0) + if_ret(1)
            assert x == 8
            """,
            arcz=".1 16 67 7.   .2 23 24 3. 45 5.", arcz_missing=""
            )

    def test_dont_confuse_exit_and_else(self):
        self.check_coverage("""\
            def foo():
                if foo:
                    a = 3
                else:
                    a = 5
                return a
            assert foo() == 3 # 7
            """,
            arcz=".1 17 7.  .2 23 36 25 56 6.", arcz_missing="25 56"
            )
        self.check_coverage("""\
            def foo():
                if foo:
                    a = 3
                else:
                    a = 5
            foo() # 6
            """,
            arcz=".1 16 6.  .2 23 3. 25 5.", arcz_missing="25 5."
            )

    if 0:   # expected failure
        def test_unused_lambdas_are_confusing_bug_90(self):
            self.check_coverage("""\
                a = 1
                fn = lambda x: x
                b = 3
                """,
                arcz=".1 12 .2 2-2 23 3."
                )


class WithTest(CoverageTest):
    """Arc-measuring tests involving context managers."""

    def test_with(self):
        self.check_coverage("""\
            def example():
                with open("test", "w") as f: # exit
                    f.write("")
                    return 1

            example()
            """,
            arcz=".1 .2 23 34 4. 16 6."
            )

    def test_bug_146(self):
        # https://bitbucket.org/ned/coveragepy/issue/146
        self.check_coverage("""\
            for i in range(2):
                with open("test", "w") as f:
                    print(3)
                print(4)
            print(5)
            """,
            arcz=".1 12 23 34 41 15 5."
            )


class LoopArcTest(CoverageTest):
    """Arc-measuring tests involving loops."""

    def test_loop(self):
        self.check_coverage("""\
            for i in range(10):
                a = i
            assert a == 9
            """,
            arcz=".1 12 21 13 3.", arcz_missing="")
        self.check_coverage("""\
            a = -1
            for i in range(0):
                a = i
            assert a == -1
            """,
            arcz=".1 12 23 32 24 4.", arcz_missing="23 32")

    def test_nested_loop(self):
        self.check_coverage("""\
            for i in range(3):
                for j in range(3):
                    a = i + j
            assert a == 4
            """,
            arcz=".1 12 23 32 21 14 4.", arcz_missing="")

    def test_break(self):
        self.check_coverage("""\
            for i in range(10):
                a = i
                break       # 3
                a = 99
            assert a == 0   # 5
            """,
            arcz=".1 12 23 35 15 41 5.", arcz_missing="15 41")

    def test_continue(self):
        self.check_coverage("""\
            for i in range(10):
                a = i
                continue    # 3
                a = 99
            assert a == 9   # 5
            """,
            arcz=".1 12 23 31 15 41 5.", arcz_missing="41")

    def test_nested_breaks(self):
        self.check_coverage("""\
            for i in range(3):
                for j in range(3):
                    a = i + j
                    break               # 4
                if i == 2:
                    break
            assert a == 2 and i == 2    # 7
            """,
            arcz=".1 12 23 34 45 25 56 51 67 17 7.", arcz_missing="17 25")

    def test_while_true(self):
        # With "while 1", the loop knows it's constant.
        self.check_coverage("""\
            a, i = 1, 0
            while 1:
                if i >= 3:
                    a = 4
                    break
                i += 1
            assert a == 4 and i == 3
            """,
            arcz=".1 12 23 34 45 36 63 57 7.",
            )
        # With "while True", 2.x thinks it's computation, 3.x thinks it's
        # constant.
        if env.PY3:
            arcz = ".1 12 23 34 45 36 63 57 7."
        else:
            arcz = ".1 12 23 27 34 45 36 62 57 7."
        self.check_coverage("""\
            a, i = 1, 0
            while True:
                if i >= 3:
                    a = 4
                    break
                i += 1
            assert a == 4 and i == 3
            """,
            arcz=arcz,
            )

    def test_for_if_else_for(self):
        self.check_coverage("""\
            def branches_2(l):
                if l:
                    for e in l:
                        a = 4
                else:
                    a = 6

            def branches_3(l):
                for x in l:
                    if x:
                        for e in l:
                            a = 12
                    else:
                        a = 14

            branches_2([0,1])
            branches_3([0,1])
            """,
            arcz=
                ".1 18 8G GH H. "
                ".2 23 34 43 26 3. 6. "
                ".9 9A 9-8 AB BC CB B9 AE E9",
            arcz_missing="26 6."
            )

    def test_for_else(self):
        self.check_coverage("""\
            def forelse(seq):
                for n in seq:
                    if n > 5:
                        break
                else:
                    print('None of the values were greater than 5')
                print('Done')
            forelse([1,2])
            forelse([1,6])
            """,
            arcz=".1 .2 23 32 34 47 26 67 7. 18 89 9."
            )

    def test_confusing_for_loop_bug_175(self):
        if env.PY3:
            # Py3 counts the list comp as a separate code object.
            arcz = ".1 .2 2-2 12 23 34 45 53 3."
        else:
            arcz = ".1 12 23 34 45 53 3."
        self.check_coverage("""\
            o = [(1,2), (3,4)]
            o = [a for a in o]
            for tup in o:
                x = tup[0]
                y = tup[1]
            """,
            arcz=arcz, arcz_missing="", arcz_unpredicted="")
        if env.PY3:
            arcz = ".1 12 .2 2-2 23 34 42 2."
        else:
            arcz = ".1 12 23 34 42 2."
        self.check_coverage("""\
            o = [(1,2), (3,4)]
            for tup in [a for a in o]:
                x = tup[0]
                y = tup[1]
            """,
            arcz=arcz, arcz_missing="", arcz_unpredicted="")


class ExceptionArcTest(CoverageTest):
    """Arc-measuring tests involving exception handling."""

    def test_try_except(self):
        self.check_coverage("""\
            a, b = 1, 1
            try:
                a = 3
            except:
                b = 5
            assert a == 3 and b == 1
            """,
            arcz=".1 12 23 36 45 56 6.", arcz_missing="45 56")
        self.check_coverage("""\
            a, b = 1, 1
            try:
                a = 3
                raise Exception("Yikes!")
                a = 5
            except:
                b = 7
            assert a == 3 and b == 7
            """,
            arcz=".1 12 23 34 58 67 78 8.",
            arcz_missing="58", arcz_unpredicted="46")

    def test_hidden_raise(self):
        self.check_coverage("""\
            a, b = 1, 1
            def oops(x):
                if x % 2: raise Exception("odd")
            try:
                a = 5
                oops(1)
                a = 7
            except:
                b = 9
            assert a == 5 and b == 9
            """,
            arcz=".1 12 .3 3-2 24 45 56 67 7A 89 9A A.",
            arcz_missing="67 7A", arcz_unpredicted="68")

    def test_except_with_type(self):
        self.check_coverage("""\
            a, b = 1, 1
            def oops(x):
                if x % 2: raise ValueError("odd")
            def try_it(x):
                try:
                    a = 6
                    oops(x)
                    a = 8
                except ValueError:
                    b = 10
                return a
            assert try_it(0) == 8   # C
            assert try_it(1) == 6   # D
            """,
            arcz=".1 12 .3 3-2 24 4C CD D. .5 56 67 78 8B 9A AB B-4",
            arcz_missing="",
            arcz_unpredicted="79")

    def test_try_finally(self):
        self.check_coverage("""\
            a, c = 1, 1
            try:
                a = 3
            finally:
                c = 5
            assert a == 3 and c == 5
            """,
            arcz=".1 12 23 35 56 6.", arcz_missing="")
        self.check_coverage("""\
            a, c, d = 1, 1, 1
            try:
                try:
                    a = 4
                finally:
                    c = 6
            except:
                d = 8
            assert a == 4 and c == 6 and d == 1    # 9
            """,
            arcz=".1 12 23 34 46 67 78 89 69 9.",
            arcz_missing="67 78 89", arcz_unpredicted="")
        self.check_coverage("""\
            a, c, d = 1, 1, 1
            try:
                try:
                    a = 4
                    raise Exception("Yikes!")
                    a = 6
                finally:
                    c = 8
            except:
                d = 10                              # A
            assert a == 4 and c == 8 and d == 10    # B
            """,
            arcz=".1 12 23 34 45 68 89 8B 9A AB B.",
            arcz_missing="68 8B", arcz_unpredicted="58")

    def test_finally_in_loop(self):
        self.check_coverage("""\
            a, c, d, i = 1, 1, 1, 99
            try:
                for i in range(5):
                    try:
                        a = 5
                        if i > 0:
                            raise Exception("Yikes!")
                        a = 8
                    finally:
                        c = 10
            except:
                d = 12                              # C
            assert a == 5 and c == 10 and d == 12   # D
            """,
            arcz=".1 12 23 34 3D 45 56 67 68 8A A3 AB BC CD D.",
            arcz_missing="3D", arcz_unpredicted="7A")
        self.check_coverage("""\
            a, c, d, i = 1, 1, 1, 99
            try:
                for i in range(5):
                    try:
                        a = 5
                        if i > 10:
                            raise Exception("Yikes!")
                        a = 8
                    finally:
                        c = 10
            except:
                d = 12                              # C
            assert a == 8 and c == 10 and d == 1    # D
            """,
            arcz=".1 12 23 34 3D 45 56 67 68 8A A3 AB BC CD D.",
            arcz_missing="67 AB BC CD", arcz_unpredicted="")


    def test_break_in_finally(self):
        self.check_coverage("""\
            a, c, d, i = 1, 1, 1, 99
            try:
                for i in range(5):
                    try:
                        a = 5
                        if i > 0:
                            break
                        a = 8
                    finally:
                        c = 10
            except:
                d = 12                              # C
            assert a == 5 and c == 10 and d == 1    # D
            """,
            arcz=".1 12 23 34 3D 45 56 67 68 7A 8A A3 AB BC CD D.",
            arcz_missing="3D AB BC CD", arcz_unpredicted="AD")

    def test_finally_in_loop_bug_92(self):
        self.check_coverage("""\
            for i in range(5):
                try:
                    j = 3
                finally:
                    f = 5
                g = 6
            h = 7
            """,
            arcz=".1 12 23 35 56 61 17 7.",
            arcz_missing="", arcz_unpredicted="")

    # "except Exception as e" is crucial here.
    def test_bug_212(self):
        # Run this test only on Py2 for now.  I hope to fix it on Py3
        # eventually...
        if env.PY3:
            self.skip("This doesn't work on Python 3")
        self.check_coverage("""\
            def b(exc):
                try:
                    while 1:
                        raise Exception(exc)    # 4
                except Exception as e:
                    if exc != 'expected':
                        raise
                    q = 8

            b('expected')
            try:
                b('unexpected')     # C
            except:
                pass
            """,
            arcz=".1 .2 1A 23 34 56 67 68 8. AB BC C. DE E.",
            arcz_missing="C.", arcz_unpredicted="45 7. CD")

    def test_except_finally(self):
        self.check_coverage("""\
            a, b, c = 1, 1, 1
            try:
                a = 3
            except:
                b = 5
            finally:
                c = 7
            assert a == 3 and b == 1 and c == 7
            """,
            arcz=".1 12 23 45 37 57 78 8.", arcz_missing="45 57")
        self.check_coverage("""\
            a, b, c = 1, 1, 1
            def oops(x):
                if x % 2: raise Exception("odd")
            try:
                a = 5
                oops(1)
                a = 7
            except:
                b = 9
            finally:
                c = 11
            assert a == 5 and b == 9 and c == 11
            """,
            arcz=".1 12 .3 3-2 24 45 56 67 7B 89 9B BC C.",
            arcz_missing="67 7B", arcz_unpredicted="68")


class YieldTest(CoverageTest):
    """Arc tests for generators."""

    def test_yield_in_loop(self):
        self.check_coverage("""\
            def gen(inp):
                for n in inp:
                    yield n

            list(gen([1,2,3]))
            """,
            arcz=".1 .2 23 2. 32 15 5.",
            arcz_missing="",
            arcz_unpredicted="")

    def test_padded_yield_in_loop(self):
        self.check_coverage("""\
            def gen(inp):
                i = 2
                for n in inp:
                    i = 4
                    yield n
                    i = 6
                i = 7

            list(gen([1,2,3]))
            """,
            arcz=".1 19 9.  .2 23 34 45 56 63 37 7.",
            arcz_missing="",
            arcz_unpredicted="")

    def test_bug_308(self):
        self.check_coverage("""\
            def run():
                for i in range(10):
                    yield lambda: i

            for f in run():
                print(f())
            """,
            arcz=".1 15 56 65 5.  .2 23 32 2.  .3 3-3",
            arcz_missing="",
            arcz_unpredicted="")

        self.check_coverage("""\
            def run():
                yield lambda: 100
                for i in range(10):
                    yield lambda: i

            for f in run():
                print(f())
            """,
            arcz=".1 16 67 76 6.  .2 23 34 43 3.   2-2  .4 4-4",
            arcz_missing="",
            arcz_unpredicted="")

        self.check_coverage("""\
            def run():
                yield lambda: 100  # no branch miss

            for f in run():
                print(f())
            """,
            arcz=".1 14 45 54 4.  .2 2.  2-2",
            arcz_missing="",
            arcz_unpredicted="")

    def test_bug_324(self):
        # This code is tricky: the list() call pulls all the values from gen(),
        # but each of them is a generator itself that is never iterated.  As a
        # result, the generator expression on line 3 is never entered or run.
        self.check_coverage("""\
            def gen(inp):
                for n in inp:
                    yield (i * 2 for i in range(n))

            list(gen([1,2,3]))
            """,
            arcz=
                ".1 15 5. "     # The module level
                ".2 23 32 2. "  # The gen() function
                ".3 3-3",       # The generator expression
            arcz_missing=".3 3-3",
            arcz_unpredicted="")

    def test_coroutines(self):
        self.check_coverage("""\
            def double_inputs():
                while [1]:      # avoid compiler differences
                    x = yield
                    x *= 2
                    yield x

            gen = double_inputs()
            next(gen)
            print(gen.send(10))
            next(gen)
            print(gen.send(6))
            """,
            arcz=
                ".1 17 78 89 9A AB B. "
                ".2 23 34 45 52 2.",
            arcz_missing="2.",
            arcz_unpredicted="")
        self.assertEqual(self.stdout(), "20\n12\n")


class MiscArcTest(CoverageTest):
    """Miscellaneous arc-measuring tests."""

    def test_dict_literal(self):
        if env.PYVERSION < (3, 5):
            arcz = ".1 19 9."
        else:
            # Python 3.5 changed how dict literals are constructed.
            arcz = ".1 19 9-2"
        self.check_coverage("""\
            d = {
                'a': 2,
                'b': 3,
                'c': {
                    'd': 5,
                    'e': 6,
                    }
                }
            assert d
            """,
            arcz=arcz)

    def test_pathologically_long_code_object(self):
        # https://bitbucket.org/ned/coveragepy/issue/359
        # The structure of this file is such that an EXTENDED_ARG byte code is
        # needed to encode the jump at the end.  We weren't interpreting those
        # opcodes.
        code = """\
            data = [
            """ + "".join("""\
                [{i}, {i}, {i}, {i}, {i}, {i}, {i}, {i}, {i}, {i}],
            """.format(i=i) for i in range(2000)
            ) + """\
            ]

            if __name__ == "__main__":
                print(len(data))
            """
        self.check_coverage(
            code,
            arcs=[(-1, 1), (1, 2004), (2004, -2), (2004, 2005), (2005, -2)],
            )


class ExcludeTest(CoverageTest):
    """Tests of exclusions to indicate known partial branches."""

    def test_default(self):
        # A number of forms of pragma comment are accepted.
        self.check_coverage("""\
            a = 1
            if a:   #pragma: no branch
                b = 3
            c = 4
            if c:   # pragma NOBRANCH
                d = 6
            e = 7
            if e:#\tpragma:\tno branch
                f = 9
            """,
            [1,2,3,4,5,6,7,8,9],
            arcz=".1 12 23 24 34 45 56 57 67 78 89 9. 8.", arcz_missing="")

    def test_custom_pragmas(self):
        self.check_coverage("""\
            a = 1
            while a:    # [only some]
                c = 3
                break
            assert c == 5-2
            """,
            [1,2,3,4,5],
            partials=["only some"],
            arcz=".1 12 23 34 45 25 5.", arcz_missing="")


class LineDataTest(CoverageTest):
    """Tests that line_data gives us what we expect."""

    def test_branch(self):
        cov = coverage.Coverage(branch=True)

        self.make_file("fun1.py", """\
            def fun1(x):
                if x == 1:
                    return

            fun1(3)
            """)

        self.start_import_stop(cov, "fun1")

        data = cov.get_data()
        fun1_lines = data.lines(abs_file("fun1.py"))
        self.assertCountEqual(fun1_lines, [1, 2, 5])
