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
« 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# --------------------------------------------------------------------------------------------------
9from __future__ import annotations
11import re
12from typing import TYPE_CHECKING
14import pytest
16from tsfpga.system_utils import create_file, read_file
17from tsfpga.vhdl_file_documentation import SEPARATOR_LINE_LENGTH
19if TYPE_CHECKING:
20 from pathlib import Path
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 )
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
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}")
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
66 def _get_comment_character(self) -> str:
67 if self._file.name.endswith(".py"):
68 return "#"
70 if self._file.name.endswith(".vhd"):
71 return "--"
73 if self._file.name.endswith((".xdc", ".tcl")):
74 return "#"
76 if self._file.name.endswith((".c", ".cpp", ".h", ".cs")):
77 return "//"
79 if self._file.name.endswith((".v", ".vh", ".sv", ".svh")):
80 return "//"
82 raise RuntimeError(f"Could not decide file ending of {self._file}")
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)
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)
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"
102 file = create_file(tmp_path / "header.vhd", header)
103 copyright_header = CopyrightHeader(file, "Apa")
104 assert copyright_header.check_file()
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()
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()
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()
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"
127 file = create_file(tmp_path / "header.vhd", header)
128 copyright_header = CopyrightHeader(file, "Apa", ["Some more", "text."])
129 assert copyright_header.check_file()
132def test_fix_file_comment_insertion(tmp_path: Path) -> None:
133 file = create_file(tmp_path / "file_for_test.vhd", "Apa\n")
135 copyright_header = CopyrightHeader(file, "Hest Hestsson")
136 copyright_header.fix_file()
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"
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")
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}"