Coverage for tsfpga/test/lint/copyright_lint.py: 47%

93 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-02-21 20:51 +0000

1# -------------------------------------------------------------------------------------------------- 

2# Copyright (c) Lukas Vik. All rights reserved. 

3# 

4# This file is part of the tsfpga project, a project platform for modern FPGA development. 

5# https://tsfpga.com 

6# https://github.com/tsfpga/tsfpga 

7# -------------------------------------------------------------------------------------------------- 

8 

9from __future__ import annotations 

10 

11import re 

12from typing import TYPE_CHECKING 

13 

14import pytest 

15 

16from tsfpga.system_utils import create_file, read_file 

17from tsfpga.vhdl_file_documentation import SEPARATOR_LINE_LENGTH 

18 

19if TYPE_CHECKING: 

20 from pathlib import Path 

21 

22 

23class CopyrightHeader: 

24 def __init__( 

25 self, file: Path, copyright_holder: str, copyright_text_lines: list[str] | None = None 

26 ) -> None: 

27 self._file = file 

28 self.comment_character = self._get_comment_character() 

29 self.separator_line = f"{self.comment_character} " + "-" * ( 

30 SEPARATOR_LINE_LENGTH - 1 - len(self.comment_character) 

31 ) 

32 self.expected_copyright_header = self._get_expected_copyright_header( 

33 copyright_holder, copyright_text_lines 

34 ) 

35 

36 def check_file(self) -> bool: 

37 """ 

38 Copyright comments should be correct. It should be followed by a blank line or another 

39 comment. 

40 """ 

41 copyright_header_re = self.expected_copyright_header.replace("(", "\\(").replace(")", "\\)") 

42 regexp = re.compile(copyright_header_re + rf"($|\n|{self.comment_character})") 

43 data = read_file(self._file) 

44 return regexp.match(data) is not None 

45 

46 def fix_file(self) -> None: 

47 if self._is_suitable_for_insertion(): 

48 self._insert_copyright_header() 

49 else: 

50 raise ValueError(f"Can not fix copyright header in file {self._file}") 

51 

52 def _get_expected_copyright_header( 

53 self, copyright_holder: str, copyright_text_lines: list[str] | None 

54 ) -> str: 

55 header = f"{self.separator_line}\n" 

56 header += ( 

57 f"{self.comment_character} Copyright (c) {copyright_holder}. All rights reserved.\n" 

58 ) 

59 if copyright_text_lines: 

60 header += f"{self.comment_character}\n" 

61 for copyright_text_line in copyright_text_lines: 

62 header += f"{self.comment_character} {copyright_text_line}\n" 

63 header += f"{self.separator_line}\n" 

64 return header 

65 

66 def _get_comment_character(self) -> str: 

67 if self._file.name.endswith(".py"): 

68 return "#" 

69 

70 if self._file.name.endswith(".vhd"): 

71 return "--" 

72 

73 if self._file.name.endswith((".xdc", ".tcl")): 

74 return "#" 

75 

76 if self._file.name.endswith((".c", ".cpp", ".h", ".cs")): 

77 return "//" 

78 

79 if self._file.name.endswith((".v", ".vh", ".sv", ".svh")): 

80 return "//" 

81 

82 raise RuntimeError(f"Could not decide file ending of {self._file}") 

83 

84 def _is_suitable_for_insertion(self) -> bool: 

85 """ 

86 If the file does not begin with a comment, we consider it suitable to insert a new 

87 copyright header comment. 

88 """ 

89 return not read_file(self._file).startswith(self.comment_character) 

90 

91 def _insert_copyright_header(self) -> None: 

92 data = read_file(self._file) 

93 data = f"{self.expected_copyright_header}\n{data}" 

94 create_file(self._file, data) 

95 

96 

97def test_check_file(tmp_path: Path) -> None: 

98 header = "-- " + "-" * 97 + "\n" 

99 header += "-- Copyright (c) Apa. All rights reserved.\n" 

100 header += "-- " + "-" * 97 + "\n" 

101 

102 file = create_file(tmp_path / "header.vhd", header) 

103 copyright_header = CopyrightHeader(file, "Apa") 

104 assert copyright_header.check_file() 

105 

106 file = create_file(tmp_path / "non_comment.vhd", header + "non-comment on line after") 

107 copyright_header = CopyrightHeader(file, "Apa") 

108 assert not copyright_header.check_file() 

109 

110 file = create_file(tmp_path / "empty_line.vhd", header + "\nEmpty line and then non-comment") 

111 copyright_header = CopyrightHeader(file, "Apa") 

112 assert copyright_header.check_file() 

113 

114 file = create_file(tmp_path / "further_comment.vhd", header + "-- Further comment\n") 

115 copyright_header = CopyrightHeader(file, "Apa") 

116 assert copyright_header.check_file() 

117 

118 

119def test_check_file_with_copyright_text(tmp_path: Path) -> None: 

120 header = "-- " + "-" * 97 + "\n" 

121 header += "-- Copyright (c) Apa. All rights reserved.\n" 

122 header += "--\n" 

123 header += "-- Some more\n" 

124 header += "-- text.\n" 

125 header += "-- " + "-" * 97 + "\n" 

126 

127 file = create_file(tmp_path / "header.vhd", header) 

128 copyright_header = CopyrightHeader(file, "Apa", ["Some more", "text."]) 

129 assert copyright_header.check_file() 

130 

131 

132def test_fix_file_comment_insertion(tmp_path: Path) -> None: 

133 file = create_file(tmp_path / "file_for_test.vhd", "Apa\n") 

134 

135 copyright_header = CopyrightHeader(file, "Hest Hestsson") 

136 copyright_header.fix_file() 

137 

138 data = read_file(file).split("\n") 

139 assert data[0] == "-- " + "-" * 97 

140 assert data[1] == "-- Copyright (c) Hest Hestsson. All rights reserved." 

141 assert data[2] == "-- " + "-" * 97 

142 assert data[3] == "" 

143 assert data[4] == "Apa" 

144 

145 

146def test_fix_file_should_not_run_on_dirty_file(tmp_path: Path) -> None: 

147 data = "-- Custom comment line\n\nApa\n" 

148 file = create_file(tmp_path / "file_for_test.vhd", data) 

149 copyright_header = CopyrightHeader(file, "A") 

150 

151 with pytest.raises(ValueError) as exception_info: # noqa: PT011 

152 copyright_header.fix_file() 

153 assert str(exception_info.value) == f"Can not fix copyright header in file {file}"