# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt

"""Test LCOV-based summary reporting for coverage.py."""

from __future__ import annotations

import math
import textwrap

from tests.coveragetest import CoverageTest

import coverage
from coverage import env


class LcovTest(CoverageTest):
    """Tests of the LCOV reports from coverage.py."""

    def create_initial_files(self) -> None:
        """
        Helper for tests that handles the common ceremony so the tests can
        show the consequences of changes in the setup.
        """
        self.make_file("main_file.py", """\
            def cuboid_volume(l):
                return (l*l*l)

            def IsItTrue():
                return True
            """)

        self.make_file("test_file.py", """\
            from main_file import cuboid_volume
            import unittest

            class TestCuboid(unittest.TestCase):
                def test_volume(self):
                    self.assertAlmostEqual(cuboid_volume(2),8)
                    self.assertAlmostEqual(cuboid_volume(1),1)
                    self.assertAlmostEqual(cuboid_volume(0),0)
                    self.assertAlmostEqual(cuboid_volume(5.5),166.375)
            """)

    def get_lcov_report_content(self, filename: str = "coverage.lcov") -> str:
        """Return the content of an LCOV report."""
        with open(filename) as file:
            return file.read()

    def test_lone_file(self) -> None:
        # For a single file with a couple of functions, the lcov should cover
        # the function definitions themselves, but not the returns.
        self.make_file("main_file.py", """\
            def cuboid_volume(l):
                return (l*l*l)

            def IsItTrue():
                return True
            """)
        expected_result = textwrap.dedent("""\
            TN:
            SF:main_file.py
            DA:1,1,7URou3io0zReBkk69lEb/Q
            DA:4,1,ilhb4KUfytxtEuClijZPlQ
            DA:2,0,Xqj6H1iz/nsARMCAbE90ng
            DA:5,0,LWILTcvARcydjFFyo9qM0A
            LF:4
            LH:2
            end_of_record
            """)
        self.assert_doesnt_exist(".coverage")
        cov = coverage.Coverage(source=["."])
        self.start_import_stop(cov, "main_file")
        pct = cov.lcov_report()
        assert pct == 50.0
        actual_result = self.get_lcov_report_content()
        assert expected_result == actual_result

    def test_simple_line_coverage_two_files(self) -> None:
        # Test that line coverage is created when coverage is run,
        # and matches the output of the file below.
        self.create_initial_files()
        self.assert_doesnt_exist(".coverage")
        self.make_file(".coveragerc", "[lcov]\noutput = data.lcov\n")
        cov = coverage.Coverage(source=".")
        self.start_import_stop(cov, "test_file")
        pct = cov.lcov_report()
        assert pct == 50.0
        self.assert_exists("data.lcov")
        expected_result = textwrap.dedent("""\
            TN:
            SF:main_file.py
            DA:1,1,7URou3io0zReBkk69lEb/Q
            DA:4,1,ilhb4KUfytxtEuClijZPlQ
            DA:2,0,Xqj6H1iz/nsARMCAbE90ng
            DA:5,0,LWILTcvARcydjFFyo9qM0A
            LF:4
            LH:2
            end_of_record
            TN:
            SF:test_file.py
            DA:1,1,R5Rb4IzmjKRgY/vFFc1TRg
            DA:2,1,E/tvV9JPVDhEcTCkgrwOFw
            DA:4,1,GP08LPBYJq8EzYveHJy2qA
            DA:5,1,MV+jSLi6PFEl+WatEAptog
            DA:6,0,qyqd1mF289dg6oQAQHA+gQ
            DA:7,0,nmEYd5F1KrxemgC9iVjlqg
            DA:8,0,jodMK26WYDizOO1C7ekBbg
            DA:9,0,LtxfKehkX8o4KvC5GnN52g
            LF:8
            LH:4
            end_of_record
            """)
        actual_result = self.get_lcov_report_content(filename="data.lcov")
        assert expected_result == actual_result

    def test_branch_coverage_one_file(self) -> None:
        # Test that the reporter produces valid branch coverage.
        self.make_file("main_file.py", """\
            def is_it_x(x):
                if x == 3:
                    return x
                else:
                    return False
            """)
        self.assert_doesnt_exist(".coverage")
        cov = coverage.Coverage(branch=True, source=".")
        self.start_import_stop(cov, "main_file")
        pct = cov.lcov_report()
        assert math.isclose(pct, 16.666666666666668)
        self.assert_exists("coverage.lcov")
        expected_result = textwrap.dedent("""\
            TN:
            SF:main_file.py
            DA:1,1,4MDXMbvwQ3L7va1tsphVzw
            DA:2,0,MuERA6EYyZNpKPqoJfzwkA
            DA:3,0,sAyiiE6iAuPMte9kyd0+3g
            DA:5,0,W/g8GJDAYJkSSurt59Mzfw
            LF:4
            LH:1
            BRDA:3,0,0,-
            BRDA:5,0,1,-
            BRF:2
            BRH:0
            end_of_record
            """)
        actual_result = self.get_lcov_report_content()
        assert expected_result == actual_result

    def test_branch_coverage_two_files(self) -> None:
        # Test that valid branch coverage is generated
        # in the case of two files.
        self.make_file("main_file.py", """\
            def is_it_x(x):
                if x == 3:
                    return x
                else:
                    return False
            """)

        self.make_file("test_file.py", """\
            from main_file import *
            import unittest

            class TestIsItX(unittest.TestCase):
                def test_is_it_x(self):
                    self.assertEqual(is_it_x(3), 3)
                    self.assertEqual(is_it_x(4), False)
            """)
        self.assert_doesnt_exist(".coverage")
        cov = coverage.Coverage(branch=True, source=".")
        self.start_import_stop(cov, "test_file")
        pct = cov.lcov_report()
        assert math.isclose(pct, 41.666666666666664)
        self.assert_exists("coverage.lcov")
        expected_result = textwrap.dedent("""\
            TN:
            SF:main_file.py
            DA:1,1,4MDXMbvwQ3L7va1tsphVzw
            DA:2,0,MuERA6EYyZNpKPqoJfzwkA
            DA:3,0,sAyiiE6iAuPMte9kyd0+3g
            DA:5,0,W/g8GJDAYJkSSurt59Mzfw
            LF:4
            LH:1
            BRDA:3,0,0,-
            BRDA:5,0,1,-
            BRF:2
            BRH:0
            end_of_record
            TN:
            SF:test_file.py
            DA:1,1,9TxKIyoBtmhopmlbDNa8FQ
            DA:2,1,E/tvV9JPVDhEcTCkgrwOFw
            DA:4,1,C3s/c8C1Yd/zoNG1GnGexg
            DA:5,1,9qPyWexYysgeKtB+YvuzAg
            DA:6,0,LycuNcdqoUhPXeuXUTf5lA
            DA:7,0,FPTWzd68bDx76HN7VHu1wA
            LF:6
            LH:4
            BRF:0
            BRH:0
            end_of_record
            """)
        actual_result = self.get_lcov_report_content()
        assert expected_result == actual_result

    def test_half_covered_branch(self) -> None:
        # Test that for a given branch that is only half covered,
        # the block numbers remain the same, and produces valid lcov.
        self.make_file("main_file.py", """\
            something = True

            if something:
                print("Yes, something")
            else:
                print("No, nothing")
            """)
        self.assert_doesnt_exist(".coverage")
        cov = coverage.Coverage(branch=True, source=".")
        self.start_import_stop(cov, "main_file")
        pct = cov.lcov_report()
        assert math.isclose(pct, 66.66666666666667)
        self.assert_exists("coverage.lcov")
        expected_result = textwrap.dedent("""\
            TN:
            SF:main_file.py
            DA:1,1,N4kbVOlkNI1rqOfCArBClw
            DA:3,1,CmlqqPf0/H+R/p7/PLEXZw
            DA:4,1,rE3mWnpoMq2W2sMETVk/uQ
            DA:6,0,+Aov7ekIts7C96udNDVIIQ
            LF:4
            LH:3
            BRDA:6,0,0,-
            BRDA:4,0,1,1
            BRF:2
            BRH:1
            end_of_record
            """)
        actual_result = self.get_lcov_report_content()
        assert expected_result == actual_result

    def test_empty_init_files(self) -> None:
        # Test that in the case of an empty __init__.py file, the lcov
        # reporter will note that the file is there, and will note the empty
        # line. It will also note the lack of branches, and the checksum for
        # the line.
        #
        # Although there are no lines found, it will note one line as hit in
        # old Pythons, and no lines hit in newer Pythons.

        self.make_file("__init__.py", "")
        self.assert_doesnt_exist(".coverage")
        cov = coverage.Coverage(branch=True, source=".")
        self.start_import_stop(cov, "__init__")
        pct = cov.lcov_report()
        assert pct == 0.0
        self.assert_exists("coverage.lcov")
        # Newer Pythons have truly empty empty files.
        if env.PYBEHAVIOR.empty_is_empty:
            expected_result = textwrap.dedent("""\
                TN:
                SF:__init__.py
                LF:0
                LH:0
                BRF:0
                BRH:0
                end_of_record
                """)
        else:
            expected_result = textwrap.dedent("""\
                TN:
                SF:__init__.py
                DA:1,1,1B2M2Y8AsgTpgAmY7PhCfg
                LF:0
                LH:0
                BRF:0
                BRH:0
                end_of_record
                """)
        actual_result = self.get_lcov_report_content()
        assert expected_result == actual_result

    def test_excluded_lines(self) -> None:
        self.make_file(".coveragerc", """\
            [report]
            exclude_lines = foo
            """)
        self.make_file("runme.py", """\
            s = "Hello 1"
            t = "foo is ignored 2"
            if s.upper() == "BYE 3":
                i_am_missing_4()
                foo_is_missing_5()
            print("Done 6")
            # foo 7
            # line 8
            """)
        cov = coverage.Coverage(source=".", branch=True)
        self.start_import_stop(cov, "runme")
        cov.lcov_report()
        expected_result = textwrap.dedent("""\
            TN:
            SF:runme.py
            DA:1,1,nWfwsz0pRTEJrInVF+xNvQ
            DA:3,1,uV4NoIauDo5LCti6agX9sg
            DA:6,1,+PfQRgSChjQOGkA6MArMDg
            DA:4,0,GR4ThLStnqpcZvm3alfRaA
            LF:4
            LH:3
            BRDA:4,0,0,-
            BRDA:6,0,1,1
            BRF:2
            BRH:1
            end_of_record
            """)
        actual_result = self.get_lcov_report_content()
        assert expected_result == actual_result
