blob: 07195fb6aae79c207f685ed279a229183cbeafd3 [file]
#!/usr/bin/env fuchsia-vendored-python
# Copyright 2026 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import collections
import io
import pathlib
import sys
import tempfile
import unittest
from collections.abc import Collection
from unittest import mock
# Add parent directory to path so we can import check_header_guards
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import check_header_guards
class TestCheckHeaderGuards(unittest.TestCase):
def setUp(self) -> None:
self.temp_dir = self.enterContext(tempfile.TemporaryDirectory())
self.root = pathlib.Path(self.temp_dir)
def test_header_guard_from_path_standard(self) -> None:
path = self.root / "src" / "lib" / "foo" / "bar.h"
guard = check_header_guards._header_guard_from_path(path, self.root)
self.assertEqual(guard, "SRC_LIB_FOO_BAR_H_")
def test_header_guard_from_path_special_chars(self) -> None:
path = self.root / "src" / "foo-bar.baz" / "qux.h"
guard = check_header_guards._header_guard_from_path(path, self.root)
self.assertEqual(guard, "SRC_FOO_BAR_BAZ_QUX_H_")
def test_header_guard_from_path_public_prefix(self) -> None:
path = (
self.root
/ "sdk"
/ "lib"
/ "fdio"
/ "include"
/ "lib"
/ "fdio"
/ "spawn.h"
)
guard = check_header_guards._header_guard_from_path(path, self.root)
self.assertEqual(guard, "LIB_FDIO_SPAWN_H_")
def test_header_guard_from_path_sysroot_prefix(self) -> None:
path = (
self.root
/ "zircon"
/ "third_party"
/ "ulib"
/ "musl"
/ "include"
/ "stdio.h"
)
guard = check_header_guards._header_guard_from_path(path, self.root)
self.assertEqual(guard, "SYSROOT_STDIO_H_")
def test_header_guard_from_path_outside_root(self) -> None:
path = pathlib.Path("/other/path/outside/root.h")
with self.assertRaises(check_header_guards.HeaderGuardError):
check_header_guards._header_guard_from_path(path, self.root)
def test_generate_fixed_content_empty(self) -> None:
fixed = check_header_guards._generate_fixed_content("", "FOO_BAR_H_")
expected = (
"#ifndef FOO_BAR_H_\n#define FOO_BAR_H_\n\n#endif // FOO_BAR_H_\n"
)
self.assertEqual(fixed, expected)
def test_generate_fixed_content_pragma_once(self) -> None:
content = "// Copyright notice\n\n#pragma once\n\nvoid foo();\n"
fixed = check_header_guards._generate_fixed_content(
content, "FOO_BAR_H_"
)
expected = (
"// Copyright notice\n\n"
"#ifndef FOO_BAR_H_\n#define FOO_BAR_H_\n\n"
"void foo();\n\n"
"#endif // FOO_BAR_H_\n"
)
self.assertEqual(fixed, expected)
def test_generate_fixed_content_multiple_pragma_once_raises(self) -> None:
content = "#pragma once\n#pragma once\n"
with self.assertRaises(check_header_guards.HeaderGuardFixError):
check_header_guards._generate_fixed_content(content, "FOO_H_")
def test_generate_fixed_content_ifndef_after_endif_raises(self) -> None:
content = "#endif\n#ifndef FOO_H_\n#define FOO_H_\n"
with self.assertRaises(check_header_guards.HeaderGuardFixError):
check_header_guards._generate_fixed_content(content, "FOO_H_")
with self.assertRaises(ValueError):
check_header_guards._generate_fixed_content(content, "FOO_H_")
def test_generate_fixed_content_unmatched_ifndef_raises(self) -> None:
content = "#ifndef FOO_H_\n#define FOO_H_\nvoid foo();\n"
with self.assertRaises(check_header_guards.HeaderGuardFixError):
check_header_guards._generate_fixed_content(content, "FOO_H_")
with self.assertRaises(ValueError):
check_header_guards._generate_fixed_content(content, "FOO_H_")
def test_generate_fixed_content_unmatched_endif_raises(self) -> None:
content = "void foo();\n#endif // FOO_H_\n"
with self.assertRaises(check_header_guards.HeaderGuardFixError):
check_header_guards._generate_fixed_content(content, "FOO_H_")
with self.assertRaises(ValueError):
check_header_guards._generate_fixed_content(content, "FOO_H_")
def test_generate_fixed_content_replaces_outer_guards(self) -> None:
content = (
"// Header comment\n"
"#ifndef OLD_GUARD_H_\n"
"#define OLD_GUARD_H_\n\n"
"#ifndef __cplusplus\n"
"#define __cplusplus\n"
"#endif\n\n"
"#endif // OLD_GUARD_H_\n"
)
fixed = check_header_guards._generate_fixed_content(
content, "NEW_GUARD_H_"
)
expected = (
"// Header comment\n"
"#ifndef NEW_GUARD_H_\n"
"#define NEW_GUARD_H_\n\n"
"#ifndef __cplusplus\n"
"#define __cplusplus\n"
"#endif\n\n"
"#endif // NEW_GUARD_H_\n"
)
self.assertEqual(fixed, expected)
def test_generate_fixed_content_insert_after_comment(self) -> None:
content = (
"// Leading license comment\n" "// line 2\n\n" "void function();\n"
)
fixed = check_header_guards._generate_fixed_content(
content, "NEW_GUARD_H_"
)
expected = (
"// Leading license comment\n"
"// line 2\n\n"
"#ifndef NEW_GUARD_H_\n#define NEW_GUARD_H_\n\n"
"void function();\n\n"
"#endif // NEW_GUARD_H_\n"
)
self.assertEqual(fixed, expected)
def test_check_file_valid_guard(self) -> None:
h_file = self.root / "src" / "test.h"
h_file.parent.mkdir(parents=True, exist_ok=True)
h_file.write_text(
"#ifndef SRC_TEST_H_\n#define SRC_TEST_H_\n\n#endif // SRC_TEST_H_\n",
encoding="utf-8",
)
tracker: dict[str, list[pathlib.Path]] = collections.defaultdict(list)
check_header_guards._check_file(
h_file,
self.root,
fix_guards=False,
emit=False,
collision_tracker=tracker,
)
self.assertEqual(tracker["SRC_TEST_H_"], [h_file])
def test_check_file_fix_atomic(self) -> None:
h_file = self.root / "src" / "wrong.h"
h_file.parent.mkdir(parents=True, exist_ok=True)
h_file.write_text("#pragma once\nvoid foo();\n", encoding="utf-8")
tracker: dict[str, list[pathlib.Path]] = collections.defaultdict(list)
with mock.patch.object(sys, "stdout", io.StringIO()):
check_header_guards._check_file(
h_file,
self.root,
fix_guards=True,
emit=False,
collision_tracker=tracker,
)
updated_content = h_file.read_text(encoding="utf-8")
self.assertIn("#ifndef SRC_WRONG_H_", updated_content)
self.assertIn("#define SRC_WRONG_H_", updated_content)
self.assertIn("#endif // SRC_WRONG_H_", updated_content)
def test_check_collisions(self) -> None:
tracker = {
"GUARD_H_": [
pathlib.Path("a/foo.h"),
pathlib.Path("b/foo.h"),
]
}
with self.assertRaises(check_header_guards.HeaderGuardError):
check_header_guards._check_collisions(tracker)
tracker_no_collision = {"GUARD_H_": [pathlib.Path("a/foo.h")]}
check_header_guards._check_collisions(tracker_no_collision)
def test_generate_fixed_content_insert_after_block_comment(self) -> None:
content = (
"/*\n"
" * Copyright 2026 The Fuchsia Authors.\n"
" */\n\n"
"void function();\n"
)
fixed = check_header_guards._generate_fixed_content(
content, "NEW_GUARD_H_"
)
expected = (
"/*\n"
" * Copyright 2026 The Fuchsia Authors.\n"
" */\n\n"
"#ifndef NEW_GUARD_H_\n#define NEW_GUARD_H_\n\n"
"void function();\n\n"
"#endif // NEW_GUARD_H_\n"
)
self.assertEqual(fixed, expected)
def test_check_file_emit_stdout(self) -> None:
h_file = self.root / "src" / "emit.h"
h_file.parent.mkdir(parents=True, exist_ok=True)
h_file.write_text("// Comment\n#pragma once\n", encoding="utf-8")
tracker: dict[str, list[pathlib.Path]] = collections.defaultdict(list)
stdout_buf = io.StringIO()
with mock.patch.object(sys, "stdout", stdout_buf):
check_header_guards._check_file(
h_file,
self.root,
fix_guards=False,
emit=True,
collision_tracker=tracker,
)
output = stdout_buf.getvalue()
self.assertIn("#ifndef SRC_EMIT_H_", output)
self.assertIn("#define SRC_EMIT_H_", output)
self.assertIn("#endif // SRC_EMIT_H_", output)
def test_check_file_non_header(self) -> None:
cc_file = self.root / "src" / "foo.cc"
tracker: dict[str, list[pathlib.Path]] = collections.defaultdict(list)
check_header_guards._check_file(
cc_file,
self.root,
fix_guards=False,
emit=False,
collision_tracker=tracker,
)
self.assertEqual(len(tracker), 0)
def test_check_file_malformed_multiple_directives(self) -> None:
h_file = self.root / "src" / "multi.h"
h_file.parent.mkdir(parents=True, exist_ok=True)
h_file.write_text(
"#ifndef SRC_MULTI_H_\n#ifndef SRC_MULTI_H_\n#define SRC_MULTI_H_\n#endif // SRC_MULTI_H_\n",
encoding="utf-8",
)
tracker: dict[str, list[pathlib.Path]] = collections.defaultdict(list)
with self.assertRaises(check_header_guards.HeaderGuardError):
check_header_guards._check_file(
h_file,
self.root,
fix_guards=False,
emit=False,
collision_tracker=tracker,
)
def test_find_header_files_prunes_third_party_and_hidden(self) -> None:
src_dir = self.root / "src"
src_dir.mkdir(parents=True, exist_ok=True)
good_h = src_dir / "good.h"
good_h.write_text(
"#ifndef SRC_GOOD_H_\n#define SRC_GOOD_H_\n\n#endif // SRC_GOOD_H_\n",
encoding="utf-8",
)
tp_dir = self.root / "third_party" / "ignored"
tp_dir.mkdir(parents=True, exist_ok=True)
(tp_dir / "bad.h").write_text("INVALID CONTENT", encoding="utf-8")
git_dir = self.root / ".git" / "ignored"
git_dir.mkdir(parents=True, exist_ok=True)
(git_dir / "bad.h").write_text("INVALID CONTENT", encoding="utf-8")
headers = check_header_guards._find_header_files(self.root)
self.assertIsInstance(headers, Collection)
self.assertEqual(tuple(headers), (good_h,))
def test_main_cli_modes(self) -> None:
invalid_h = self.root / "src" / "invalid.h"
invalid_h.parent.mkdir(parents=True, exist_ok=True)
invalid_h.write_text("bad content\n", encoding="utf-8")
# In check mode without --fix or --emit on a file needing a fix
with mock.patch.object(
sys,
"argv",
[
"check_header_guards.py",
"--root",
str(self.root),
str(invalid_h),
],
), mock.patch.object(sys, "stderr", io.StringIO()):
ret = check_header_guards.main()
self.assertEqual(ret, 1)
# In --fix mode
with mock.patch.object(
sys,
"argv",
[
"check_header_guards.py",
"--root",
str(self.root),
"--fix",
str(invalid_h),
],
), mock.patch.object(sys, "stdout", io.StringIO()), mock.patch.object(
sys, "stderr", io.StringIO()
):
ret = check_header_guards.main()
self.assertEqual(ret, 0)
fixed_text = invalid_h.read_text(encoding="utf-8")
self.assertIn("#ifndef SRC_INVALID_H_", fixed_text)
def test_check_file_conflicting_pragma_and_guards(self) -> None:
h_file = self.root / "src" / "conflict.h"
h_file.parent.mkdir(parents=True, exist_ok=True)
h_file.write_text(
"#pragma once\n#ifndef SRC_CONFLICT_H_\n#define SRC_CONFLICT_H_\n#endif // SRC_CONFLICT_H_\n",
encoding="utf-8",
)
tracker: dict[str, list[pathlib.Path]] = collections.defaultdict(list)
with self.assertRaises(check_header_guards.HeaderGuardError):
check_header_guards._check_file(
h_file,
self.root,
fix_guards=False,
emit=False,
collision_tracker=tracker,
)
def test_check_file_missing_endif_comment(self) -> None:
h_file = self.root / "src" / "missing_comment.h"
h_file.parent.mkdir(parents=True, exist_ok=True)
h_file.write_text(
"#ifndef SRC_MISSING_COMMENT_H_\n#define SRC_MISSING_COMMENT_H_\n\n#endif\n",
encoding="utf-8",
)
tracker: dict[str, list[pathlib.Path]] = collections.defaultdict(list)
with self.assertRaises(check_header_guards.HeaderGuardError):
check_header_guards._check_file(
h_file,
self.root,
fix_guards=False,
emit=False,
collision_tracker=tracker,
)
def test_main_cli_nonexistent_path(self) -> None:
nonexistent = self.root / "src" / "does_not_exist.h"
with mock.patch.object(
sys,
"argv",
[
"check_header_guards.py",
"--root",
str(self.root),
str(nonexistent),
],
), mock.patch.object(sys, "stderr", io.StringIO()):
ret = check_header_guards.main()
self.assertEqual(ret, 1)
if __name__ == "__main__":
unittest.main()