Coverage for tsfpga/test/lint/copyright_lint.py: 47%
91 statements
« prev ^ index » next coverage.py v7.6.7, created at 2024-11-20 20:51 +0000
« prev ^ index » next coverage.py v7.6.7, created at 2024-11-20 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# --------------------------------------------------------------------------------------------------
9# Standard libraries
10import re
11from pathlib import Path
12from typing import Optional
14# Third party libraries
15import pytest
17# First party libraries
18from tsfpga.system_utils import create_file, read_file
19from tsfpga.vhdl_file_documentation import SEPARATOR_LINE_LENGTH
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 )
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
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}")
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
65 def _get_comment_character(self) -> str:
66 if self._file.name.endswith(".py"):
67 return "#"
69 if self._file.name.endswith(".vhd"):
70 return "--"
72 if self._file.name.endswith((".xdc", ".tcl")):
73 return "#"
75 if self._file.name.endswith((".c", ".cpp", ".h")):
76 return "//"
78 if self._file.name.endswith((".v", ".vh", ".sv", ".svh")):
79 return "//"
81 raise RuntimeError(f"Could not decide file ending of {self._file}")
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)
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)
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"
101 file = create_file(tmp_path / "header.vhd", header)
102 copyright_header = CopyrightHeader(file, "Apa")
103 assert copyright_header.check_file()
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()
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()
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()
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"
126 file = create_file(tmp_path / "header.vhd", header)
127 copyright_header = CopyrightHeader(file, "Apa", ["Some more", "text."])
128 assert copyright_header.check_file()
131def test_fix_file_comment_insertion(tmp_path: Path) -> None:
132 file = create_file(tmp_path / "file_for_test.vhd", "Apa\n")
134 copyright_header = CopyrightHeader(file, "Hest Hestsson")
135 copyright_header.fix_file()
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"
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")
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}"