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

91 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-20 20:52 +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 

9# Standard libraries 

10import re 

11from pathlib import Path 

12from typing import Optional 

13 

14# Third party libraries 

15import pytest 

16 

17# First party libraries 

18from tsfpga.system_utils import create_file, read_file 

19from tsfpga.vhdl_file_documentation import SEPARATOR_LINE_LENGTH 

20 

21 

22class CopyrightHeader: 

23 def __init__( 

24 self, file: Path, copyright_holder: str, copyright_text_lines: Optional[list[str]] = None 

25 ): 

26 self._file = file 

27 self.comment_character = self._get_comment_character() 

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

29 SEPARATOR_LINE_LENGTH - 1 - len(self.comment_character) 

30 ) 

31 self.expected_copyright_header = self._get_expected_copyright_header( 

32 copyright_holder, copyright_text_lines 

33 ) 

34 

35 def check_file(self) -> bool: 

36 """ 

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

38 comment. 

39 """ 

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

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

42 data = read_file(self._file) 

43 return regexp.match(data) is not None 

44 

45 def fix_file(self) -> None: 

46 if self._is_suitable_for_insertion(): 

47 self._insert_copyright_header() 

48 else: 

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

50 

51 def _get_expected_copyright_header( 

52 self, copyright_holder: str, copyright_text_lines: Optional[list[str]] 

53 ) -> str: 

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

55 header += ( 

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

57 ) 

58 if copyright_text_lines: 

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

60 for copyright_text_line in copyright_text_lines: 

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

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

63 return header 

64 

65 def _get_comment_character(self) -> str: 

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

67 return "#" 

68 

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

70 return "--" 

71 

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

73 return "#" 

74 

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

76 return "//" 

77 

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

79 return "//" 

80 

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

82 

83 def _is_suitable_for_insertion(self) -> bool: 

84 """ 

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

86 copyright header comment. 

87 """ 

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

89 

90 def _insert_copyright_header(self) -> None: 

91 data = read_file(self._file) 

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

93 create_file(self._file, data) 

94 

95 

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

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

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

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

100 

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

102 copyright_header = CopyrightHeader(file, "Apa") 

103 assert copyright_header.check_file() 

104 

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

106 copyright_header = CopyrightHeader(file, "Apa") 

107 assert not copyright_header.check_file() 

108 

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

110 copyright_header = CopyrightHeader(file, "Apa") 

111 assert copyright_header.check_file() 

112 

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

114 copyright_header = CopyrightHeader(file, "Apa") 

115 assert copyright_header.check_file() 

116 

117 

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

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

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

121 header += "--\n" 

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

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

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

125 

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

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

128 assert copyright_header.check_file() 

129 

130 

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

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

133 

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

135 copyright_header.fix_file() 

136 

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

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

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

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

141 assert data[3] == "" 

142 assert data[4] == "Apa" 

143 

144 

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

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

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

148 copyright_header = CopyrightHeader(file, "A") 

149 

150 with pytest.raises(ValueError) as exception_info: 

151 copyright_header.fix_file() 

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