|
5 | 5 |
|
6 | 6 | import os |
7 | 7 | import subprocess |
| 8 | +import tempfile |
8 | 9 | import xml.etree.ElementTree as etree |
9 | 10 | from io import BytesIO, StringIO |
10 | 11 | from subprocess import Popen |
|
18 | 19 | from diff_cover.violationsreporters.violations_reporter import ( |
19 | 20 | CppcheckDriver, |
20 | 21 | EslintDriver, |
| 22 | + LcovCoverageReporter, |
21 | 23 | PylintDriver, |
22 | 24 | Violation, |
23 | 25 | XmlCoverageReporter, |
@@ -794,6 +796,201 @@ def _coverage_xml(self, file_paths, violations, measured): |
794 | 796 | return root |
795 | 797 |
|
796 | 798 |
|
| 799 | +class TestLcovCoverageReporterTest: |
| 800 | + MANY_VIOLATIONS = { |
| 801 | + Violation(3, None), |
| 802 | + Violation(7, None), |
| 803 | + Violation(11, None), |
| 804 | + Violation(13, None), |
| 805 | + } |
| 806 | + FEW_MEASURED = {2, 3, 5, 7, 11, 13} |
| 807 | + |
| 808 | + FEW_VIOLATIONS = {Violation(3, None), Violation(11, None)} |
| 809 | + MANY_MEASURED = {2, 3, 5, 7, 11, 13, 17} |
| 810 | + |
| 811 | + ONE_VIOLATION = {Violation(11, None)} |
| 812 | + VERY_MANY_MEASURED = {2, 3, 5, 7, 11, 13, 17, 23, 24, 25, 26, 26, 27} |
| 813 | + |
| 814 | + @pytest.fixture(autouse=True) |
| 815 | + def patch_git_patch(self, mocker): |
| 816 | + # Paths generated by git_path are always the given argument |
| 817 | + _git_path_mock = mocker.patch( |
| 818 | + "diff_cover.violationsreporters.violations_reporter.GitPathTool" |
| 819 | + ) |
| 820 | + _git_path_mock.relative_path = lambda path: path |
| 821 | + _git_path_mock.absolute_path = lambda path: path |
| 822 | + |
| 823 | + def test_violations(self): |
| 824 | + # Construct the LCOV report |
| 825 | + file_paths = ["file1.java", "subdir/file2.java"] |
| 826 | + violations = self.MANY_VIOLATIONS |
| 827 | + measured = self.FEW_MEASURED |
| 828 | + lcov = self._coverage_lcov(file_paths, violations, measured) |
| 829 | + |
| 830 | + # Parse the report |
| 831 | + coverage = LcovCoverageReporter([lcov]) |
| 832 | + |
| 833 | + # Expect that the name is set |
| 834 | + assert coverage.name() == "LCOV" |
| 835 | + |
| 836 | + # By construction, each file has the same set |
| 837 | + # of covered/uncovered lines |
| 838 | + assert violations == coverage.violations("file1.java") |
| 839 | + assert measured == coverage.measured_lines("file1.java") |
| 840 | + |
| 841 | + # Try getting a smaller range |
| 842 | + result = coverage.violations("subdir/file2.java") |
| 843 | + assert result == violations |
| 844 | + |
| 845 | + # Once more on the first file (for caching) |
| 846 | + result = coverage.violations("file1.java") |
| 847 | + assert result == violations |
| 848 | + |
| 849 | + def test_two_inputs_first_violate(self): |
| 850 | + # Construct the LCOV report |
| 851 | + file_paths = ["file1.java"] |
| 852 | + |
| 853 | + violations1 = self.MANY_VIOLATIONS |
| 854 | + violations2 = self.FEW_VIOLATIONS |
| 855 | + |
| 856 | + measured1 = self.FEW_MEASURED |
| 857 | + measured2 = self.MANY_MEASURED |
| 858 | + |
| 859 | + lcov = self._coverage_lcov(file_paths, violations1, measured1) |
| 860 | + lcov2 = self._coverage_lcov(file_paths, violations2, measured2) |
| 861 | + |
| 862 | + # Parse the report |
| 863 | + coverage = LcovCoverageReporter([lcov, lcov2]) |
| 864 | + |
| 865 | + # By construction, each file has the same set |
| 866 | + # of covered/uncovered lines |
| 867 | + assert violations1 & violations2 == coverage.violations("file1.java") |
| 868 | + |
| 869 | + assert measured1 | measured2 == coverage.measured_lines("file1.java") |
| 870 | + |
| 871 | + def test_two_inputs_second_violate(self): |
| 872 | + # Construct the LCOV report |
| 873 | + file_paths = ["file1.java"] |
| 874 | + |
| 875 | + violations1 = self.MANY_VIOLATIONS |
| 876 | + violations2 = self.FEW_VIOLATIONS |
| 877 | + |
| 878 | + measured1 = self.FEW_MEASURED |
| 879 | + measured2 = self.MANY_MEASURED |
| 880 | + |
| 881 | + lcov = self._coverage_lcov(file_paths, violations1, measured1) |
| 882 | + lcov2 = self._coverage_lcov(file_paths, violations2, measured2) |
| 883 | + |
| 884 | + # Parse the report |
| 885 | + coverage = LcovCoverageReporter([lcov2, lcov]) |
| 886 | + |
| 887 | + # By construction, each file has the same set |
| 888 | + # of covered/uncovered lines |
| 889 | + assert violations1 & violations2 == coverage.violations("file1.java") |
| 890 | + |
| 891 | + assert measured1 | measured2 == coverage.measured_lines("file1.java") |
| 892 | + |
| 893 | + def test_three_inputs(self): |
| 894 | + # Construct the LCOV report |
| 895 | + file_paths = ["file1.java"] |
| 896 | + |
| 897 | + violations1 = self.MANY_VIOLATIONS |
| 898 | + violations2 = self.FEW_VIOLATIONS |
| 899 | + violations3 = self.ONE_VIOLATION |
| 900 | + |
| 901 | + measured1 = self.FEW_MEASURED |
| 902 | + measured2 = self.MANY_MEASURED |
| 903 | + measured3 = self.VERY_MANY_MEASURED |
| 904 | + |
| 905 | + lcov = self._coverage_lcov(file_paths, violations1, measured1) |
| 906 | + lcov2 = self._coverage_lcov(file_paths, violations2, measured2) |
| 907 | + lcov3 = self._coverage_lcov(file_paths, violations3, measured3) |
| 908 | + |
| 909 | + # Parse the report |
| 910 | + coverage = LcovCoverageReporter([lcov2, lcov, lcov3]) |
| 911 | + |
| 912 | + # By construction, each file has the same set |
| 913 | + # of covered/uncovered lines |
| 914 | + assert violations1 & violations2 & violations3 == coverage.violations( |
| 915 | + "file1.java" |
| 916 | + ) |
| 917 | + |
| 918 | + assert measured1 | measured2 | measured3 == coverage.measured_lines( |
| 919 | + "file1.java" |
| 920 | + ) |
| 921 | + |
| 922 | + def test_different_files_in_inputs(self): |
| 923 | + # Construct the LCOV report |
| 924 | + lcov_repots = [ |
| 925 | + self._coverage_lcov(["file.java"], self.MANY_VIOLATIONS, self.FEW_MEASURED), |
| 926 | + self._coverage_lcov( |
| 927 | + ["other_file.java"], self.FEW_VIOLATIONS, self.MANY_MEASURED |
| 928 | + ), |
| 929 | + ] |
| 930 | + |
| 931 | + # Parse the report |
| 932 | + coverage = LcovCoverageReporter(lcov_repots) |
| 933 | + |
| 934 | + assert self.MANY_VIOLATIONS == coverage.violations("file.java") |
| 935 | + assert self.FEW_VIOLATIONS == coverage.violations("other_file.java") |
| 936 | + |
| 937 | + def test_empty_violations(self): |
| 938 | + """ |
| 939 | + Test that an empty violations report is handled properly |
| 940 | + """ |
| 941 | + # Construct the LCOV report |
| 942 | + file_paths = ["file1.java"] |
| 943 | + |
| 944 | + violations1 = self.MANY_VIOLATIONS |
| 945 | + violations2 = set() |
| 946 | + |
| 947 | + measured1 = self.FEW_MEASURED |
| 948 | + measured2 = self.MANY_MEASURED |
| 949 | + |
| 950 | + lcov = self._coverage_lcov(file_paths, violations1, measured1) |
| 951 | + lcov2 = self._coverage_lcov(file_paths, violations2, measured2) |
| 952 | + |
| 953 | + # Parse the report |
| 954 | + coverage = LcovCoverageReporter([lcov2, lcov]) |
| 955 | + |
| 956 | + # By construction, each file has the same set |
| 957 | + # of covered/uncovered lines |
| 958 | + assert violations1 & violations2 == coverage.violations("file1.java") |
| 959 | + |
| 960 | + assert measured1 | measured2 == coverage.measured_lines("file1.java") |
| 961 | + |
| 962 | + def test_no_such_file(self): |
| 963 | + # Construct the LCOV report with no source files |
| 964 | + lcov = self._coverage_lcov([], [], []) |
| 965 | + |
| 966 | + # Parse the report |
| 967 | + coverage = LcovCoverageReporter(lcov) |
| 968 | + |
| 969 | + # Expect that we get no results |
| 970 | + result = coverage.violations("file.java") |
| 971 | + assert result == set() |
| 972 | + |
| 973 | + def _coverage_lcov(self, file_paths, violations, measured): |
| 974 | + """ |
| 975 | + Build an LCOV document based on the provided arguments. |
| 976 | + """ |
| 977 | + |
| 978 | + violation_lines = {violation.line for violation in violations} |
| 979 | + |
| 980 | + with tempfile.NamedTemporaryFile("w", delete=False) as f: |
| 981 | + for file_path in file_paths: |
| 982 | + f.write(f"SF:{file_path}\n") |
| 983 | + for line_num in measured: |
| 984 | + f.write( |
| 985 | + f"DA:{line_num},{0 if line_num in violation_lines else 1}\n" |
| 986 | + ) |
| 987 | + f.write("end_of_record\n") |
| 988 | + try: |
| 989 | + return LcovCoverageReporter.parse(f.name) |
| 990 | + finally: |
| 991 | + os.unlink(f.name) |
| 992 | + |
| 993 | + |
797 | 994 | class TestPycodestyleQualityReporterTest: |
798 | 995 | def test_quality(self, mocker, process_patcher): |
799 | 996 | # Patch the output of `pycodestyle` |
|
0 commit comments