Simplify file header updates (#836)
This commit is contained in:
+1
-1
@@ -28,4 +28,4 @@
|
||||
# ├── test_summarize_pr.py
|
||||
# └── ...
|
||||
|
||||
__version__ = "0.2.37"
|
||||
__version__ = "0.2.38"
|
||||
|
||||
@@ -120,13 +120,9 @@ def update_file(file_path, prefix, block_start, block_end, base_header):
|
||||
|
||||
# Check for special first line
|
||||
special_line_index = -1
|
||||
first_line = lines[0].lstrip("\ufeff") if lines else ""
|
||||
first_line = lines[0].lstrip("\ufeff").lower() if lines else ""
|
||||
encoding_cookie = r"^[ \t\f]*#.*?coding[:=][ \t]*[-_.a-zA-Z0-9]+"
|
||||
if lines and (
|
||||
first_line.startswith(("#!", "<?xml"))
|
||||
or first_line.lower().startswith("<!doctype")
|
||||
or re.match(encoding_cookie, first_line)
|
||||
):
|
||||
if first_line.startswith(("#!", "<?xml", "<!doctype")) or re.match(encoding_cookie, first_line):
|
||||
special_line_index = 0
|
||||
prefix_lines.append(lines[0])
|
||||
if first_line.startswith("#!") and len(lines) > 1 and re.match(encoding_cookie, lines[1]):
|
||||
@@ -136,14 +132,12 @@ def update_file(file_path, prefix, block_start, block_end, base_header):
|
||||
start_idx = special_line_index + 1 if special_line_index >= 0 else 0
|
||||
end_idx = min(start_idx + 5, len(lines)) # Look in first few lines
|
||||
|
||||
# An existing header must be the first non-empty line and use the file's comment syntax.
|
||||
candidate_index = next((i for i in range(start_idx, end_idx) if lines[i].strip()), -1)
|
||||
candidate = lines[candidate_index].strip() if candidate_index >= 0 else ""
|
||||
comment_start = (prefix or block_start or "# ").strip()
|
||||
known_header = candidate.startswith(comment_start) and re.search(
|
||||
r"AGPL-3\.0 License|CONFIDENTIAL: Unauthorized use|©\s*2014[–-]\d{4}\s+Ultralytics Inc\.|"
|
||||
r"Ultralytics Inc\..*Copyright ©\s*2014[–-]\d{4}\s*-\s*CONFIDENTIAL\s*-",
|
||||
candidate,
|
||||
header = candidate[len(comment_start) :].strip() if candidate.startswith(comment_start) else ""
|
||||
known_header = header.startswith(("Ultralytics 🚀 AGPL-3.0 License", "Ultralytics Inc. 🚀 Copyright")) or (
|
||||
header.startswith("© 2014-") and " Ultralytics Inc." in header
|
||||
)
|
||||
header_index = candidate_index if candidate == formatted_header.strip() or known_header else -1
|
||||
|
||||
|
||||
@@ -1,259 +0,0 @@
|
||||
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
||||
"""Tests for the file headers update functionality."""
|
||||
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
from unittest.mock import patch
|
||||
|
||||
from actions.update_file_headers import COMMENT_MAP, IGNORE_PATHS, main, update_file
|
||||
|
||||
|
||||
def test_update_file_python():
|
||||
"""Test updating Python file headers."""
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
# Create a test Python file
|
||||
test_file = Path(tmp_dir) / "test.py"
|
||||
test_file.write_text("print('Hello World')\n")
|
||||
|
||||
# Update file
|
||||
result = update_file(test_file, "# ", None, None, "Ultralytics 🚀 Test Header")
|
||||
|
||||
# Check results
|
||||
assert result is True
|
||||
content = test_file.read_text()
|
||||
assert content.startswith("# Ultralytics 🚀 Test Header\n\n")
|
||||
assert "print('Hello World')" in content
|
||||
|
||||
|
||||
def test_update_file_cpp():
|
||||
"""Test updating C++ file headers."""
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
# Create a test C++ file
|
||||
test_file = Path(tmp_dir) / "test.cpp"
|
||||
test_file.write_text("#include <iostream>\n\nint main() {\n return 0;\n}\n")
|
||||
|
||||
# Update file
|
||||
result = update_file(test_file, "// ", "/* ", " */", "Ultralytics 🚀 Test Header")
|
||||
|
||||
# Check results
|
||||
assert result is True
|
||||
content = test_file.read_text()
|
||||
assert content.startswith("// Ultralytics 🚀 Test Header\n\n")
|
||||
assert "#include <iostream>" in content
|
||||
|
||||
|
||||
def test_update_file_with_existing_header():
|
||||
"""Test updating file with existing header."""
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
# Create a test file with existing header
|
||||
test_file = Path(tmp_dir) / "test.py"
|
||||
test_file.write_text("# Ultralytics 🚀 AGPL-3.0 License\n\ndef main():\n pass\n")
|
||||
|
||||
# Update file
|
||||
result = update_file(test_file, "# ", None, None, "Ultralytics 🚀 Test Header")
|
||||
|
||||
# Check results
|
||||
assert result is True
|
||||
content = test_file.read_text()
|
||||
assert content.startswith("# Ultralytics 🚀 Test Header\n\n")
|
||||
assert "def main():" in content
|
||||
|
||||
|
||||
def test_update_file_with_shebang():
|
||||
"""Test updating file with shebang."""
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
# Create a test file with shebang
|
||||
test_file = Path(tmp_dir) / "test.py"
|
||||
test_file.write_text("#!/usr/bin/env python3\n\nprint('Hello World')\n")
|
||||
|
||||
# Update file
|
||||
result = update_file(test_file, "# ", None, None, "Ultralytics 🚀 Test Header")
|
||||
|
||||
# Check results
|
||||
assert result is True
|
||||
content = test_file.read_text()
|
||||
assert content.startswith("#!/usr/bin/env python3\n# Ultralytics 🚀 Test Header\n\n")
|
||||
assert "print('Hello World')" in content
|
||||
|
||||
|
||||
def test_update_file_preserves_early_ultralytics_source():
|
||||
"""Test that Ultralytics references in source code are not mistaken for headers."""
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
tmp_path = Path(tmp_dir)
|
||||
docstring_file = tmp_path / "api.py"
|
||||
docstring_file.write_text('"""Ultralytics Platform API client."""\n\nVALUE = 1\n')
|
||||
import_file = tmp_path / "__init__.py"
|
||||
import_file.write_text("from ultralytics import YOLO\n")
|
||||
|
||||
for test_file, spacing in ((docstring_file, ""), (import_file, "\n")):
|
||||
original = test_file.read_text()
|
||||
assert update_file(test_file, "# ", None, None, "Ultralytics 🚀 Test Header") is True
|
||||
assert test_file.read_text() == f"# Ultralytics 🚀 Test Header\n{spacing}{original}"
|
||||
|
||||
|
||||
def test_update_file_with_lowercase_doctype():
|
||||
"""Test that Prettier's lowercase HTML doctype remains first."""
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
test_file = Path(tmp_dir) / "index.html"
|
||||
test_file.write_text("<!doctype html>\n<html></html>\n")
|
||||
|
||||
assert update_file(test_file, None, "<!-- ", " -->", "Ultralytics 🚀 Test Header") is True
|
||||
assert test_file.read_text().startswith("<!doctype html>\n<!-- Ultralytics 🚀 Test Header -->\n\n")
|
||||
|
||||
|
||||
def test_update_file_with_bom_xml_declaration():
|
||||
"""Test that a BOM-prefixed XML declaration remains first."""
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
test_file = Path(tmp_dir) / "layout.xml"
|
||||
test_file.write_text('\ufeff<?xml version="1.0" encoding="utf-8"?>\n<root />\n')
|
||||
|
||||
assert update_file(test_file, None, "<!-- ", " -->", "Ultralytics 🚀 Test Header") is True
|
||||
assert test_file.read_text().startswith(
|
||||
'\ufeff<?xml version="1.0" encoding="utf-8"?>\n<!-- Ultralytics 🚀 Test Header -->\n\n'
|
||||
)
|
||||
|
||||
|
||||
def test_update_file_with_shebang_and_encoding_cookie():
|
||||
"""Test that a Python encoding cookie remains on line two and the header stays idempotent."""
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
test_file = Path(tmp_dir) / "script.py"
|
||||
test_file.write_text(
|
||||
"#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n# Ultralytics 🚀 AGPL-3.0 License\n\nprint('hello')\n"
|
||||
)
|
||||
|
||||
assert update_file(test_file, "# ", None, None, "Ultralytics 🚀 Test Header") is True
|
||||
assert test_file.read_text().startswith(
|
||||
"#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n# Ultralytics 🚀 Test Header\n\n"
|
||||
)
|
||||
assert update_file(test_file, "# ", None, None, "Ultralytics 🚀 Test Header") is False
|
||||
|
||||
|
||||
def test_update_file_replaces_legacy_private_header():
|
||||
"""Test that the previously emitted private header is replaced rather than duplicated."""
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
test_file = Path(tmp_dir) / "app.py"
|
||||
test_file.write_text(
|
||||
"# Ultralytics Inc. 🚀 Copyright © 2014-2025 - CONFIDENTIAL - "
|
||||
"https://ultralytics.com - All Rights Reserved\n\n"
|
||||
"print('hello')\n"
|
||||
)
|
||||
|
||||
assert update_file(test_file, "# ", None, None, "© 2014-2026 Ultralytics Inc. CONFIDENTIAL") is True
|
||||
assert test_file.read_text().startswith("# © 2014-2026 Ultralytics Inc. CONFIDENTIAL\n\n")
|
||||
assert "Copyright" not in test_file.read_text()
|
||||
|
||||
|
||||
def test_update_file_no_changes():
|
||||
"""Test updating file with no changes needed."""
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
# Create a test file with correct header
|
||||
test_file = Path(tmp_dir) / "test.py"
|
||||
test_file.write_text("# Ultralytics 🚀 Test Header\n\nprint('Hello World')\n")
|
||||
|
||||
# Update file
|
||||
result = update_file(test_file, "# ", None, None, "Ultralytics 🚀 Test Header")
|
||||
|
||||
# Check results
|
||||
assert result is False # No changes made
|
||||
|
||||
|
||||
def test_update_file_edge_cases():
|
||||
"""Test header handling for empty, unreadable, and block-comment files."""
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
tmp_path = Path(tmp_dir)
|
||||
empty_file = tmp_path / "empty.py"
|
||||
empty_file.write_text("")
|
||||
assert update_file(empty_file, "# ", None, None, "Ultralytics 🚀 Test Header") is False
|
||||
assert update_file(tmp_path, "# ", None, None, "Ultralytics 🚀 Test Header") is False
|
||||
|
||||
css_file = tmp_path / "style.css"
|
||||
css_file.write_text("body { color: black; }\n")
|
||||
assert update_file(css_file, None, "/* ", " */", "Ultralytics 🚀 Test Header") is True
|
||||
assert css_file.read_text().startswith("/* Ultralytics 🚀 Test Header */\n\n")
|
||||
|
||||
|
||||
def test_comment_map_coverage():
|
||||
"""Test that all supported file extensions have defined comment styles."""
|
||||
# Check for a few key extensions
|
||||
assert ".py" in COMMENT_MAP
|
||||
assert ".cpp" in COMMENT_MAP
|
||||
assert ".js" in COMMENT_MAP
|
||||
assert ".html" in COMMENT_MAP
|
||||
|
||||
# Check comment style format
|
||||
for ext, (prefix, block_start, block_end) in COMMENT_MAP.items():
|
||||
assert isinstance(ext, str)
|
||||
assert prefix is None or isinstance(prefix, str)
|
||||
assert block_start is None or isinstance(block_start, str)
|
||||
assert block_end is None or isinstance(block_end, str)
|
||||
|
||||
|
||||
def test_ignore_paths():
|
||||
"""Test that the ignore paths list exists and contains expected entries."""
|
||||
assert isinstance(IGNORE_PATHS, set)
|
||||
assert ".git" in IGNORE_PATHS
|
||||
assert "__pycache__" in IGNORE_PATHS
|
||||
|
||||
|
||||
def test_main_real_files():
|
||||
"""Test main function on actual repository files."""
|
||||
with patch("actions.update_file_headers.update_file", return_value=False) as mock_update, patch(
|
||||
"actions.update_file_headers.Action"
|
||||
) as mock_action:
|
||||
mock_action.return_value.repository = "ultralytics/actions"
|
||||
mock_action.return_value.is_repo_private.return_value = False
|
||||
main()
|
||||
assert mock_update.call_count > 0
|
||||
|
||||
|
||||
def test_main_with_custom_header():
|
||||
"""Test main function with custom header environment variable."""
|
||||
with patch("actions.update_file_headers.update_file", return_value=False), patch(
|
||||
"actions.update_file_headers.HEADER", "Custom Test Header"
|
||||
), patch("actions.update_file_headers.Action") as mock_action:
|
||||
mock_event = mock_action.return_value
|
||||
mock_event.repository = "test/repo"
|
||||
main()
|
||||
mock_action.assert_called_once()
|
||||
|
||||
|
||||
def test_main_private_and_skipped_repos():
|
||||
"""Test main selects private headers and skips repos without a header source."""
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
tmp_path = Path(tmp_dir)
|
||||
(tmp_path / "app.py").write_text("print('hello')\n")
|
||||
|
||||
with patch("actions.update_file_headers.Path.cwd", return_value=tmp_path), patch(
|
||||
"actions.update_file_headers.update_file", return_value=False
|
||||
) as mock_update, patch("actions.update_file_headers.Action") as mock_action:
|
||||
mock_event = mock_action.return_value
|
||||
mock_event.repository = "ultralytics/private"
|
||||
mock_event.is_repo_private.return_value = True
|
||||
main()
|
||||
assert any("CONFIDENTIAL" in call.args[4] for call in mock_update.call_args_list)
|
||||
|
||||
with patch("actions.update_file_headers.update_file", return_value=False) as mock_update, patch(
|
||||
"actions.update_file_headers.HEADER", None
|
||||
), patch("actions.update_file_headers.Action") as mock_action:
|
||||
mock_action.return_value.repository = "other/repo"
|
||||
main()
|
||||
mock_action.return_value.is_repo_private.assert_not_called()
|
||||
mock_update.assert_not_called()
|
||||
|
||||
|
||||
def test_main_updates_files_in_current_directory():
|
||||
"""Test main updates supported files under the current directory."""
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
tmp_path = Path(tmp_dir)
|
||||
test_file = tmp_path / "app.py"
|
||||
test_file.write_text("print('hello')\n")
|
||||
|
||||
with patch("actions.update_file_headers.Path.cwd", return_value=tmp_path), patch(
|
||||
"actions.update_file_headers.Action"
|
||||
) as mock_action:
|
||||
mock_event = mock_action.return_value
|
||||
mock_event.repository = "ultralytics/actions"
|
||||
mock_event.is_repo_private.return_value = False
|
||||
main()
|
||||
|
||||
assert test_file.read_text().startswith("# Ultralytics 🚀 AGPL-3.0 License")
|
||||
Reference in New Issue
Block a user