| #!/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 json | |
| 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) | |
| def test_main_cli_json_mode(self) -> None: | |
| good_h = self.root / "src" / "good.h" | |
| good_h.parent.mkdir(parents=True, exist_ok=True) | |
| good_h.write_text( | |
| "#ifndef SRC_GOOD_H_\n#define SRC_GOOD_H_\n\n#endif // SRC_GOOD_H_\n", | |
| encoding="utf-8", | |
| ) | |
| pragma_h = self.root / "src" / "pragma.h" | |
| pragma_h.write_text("#pragma once\n", encoding="utf-8") | |
| broken_h = self.root / "src" / "broken.h" | |
| broken_h.write_text("#pragma once\n#pragma once\n", encoding="utf-8") | |
| stdout_buf = io.StringIO() | |
| with mock.patch.object( | |
| sys, | |
| "argv", | |
| [ | |
| "check_header_guards.py", | |
| "--root", | |
| str(self.root), | |
| "--json", | |
| str(good_h), | |
| str(pragma_h), | |
| str(broken_h), | |
| ], | |
| ), mock.patch.object(sys, "stdout", stdout_buf): | |
| ret = check_header_guards.main() | |
| self.assertEqual(ret, 0) | |
| results = json.loads(stdout_buf.getvalue()) | |
| self.assertEqual(len(results), 2) | |
| self.assertEqual(results[0]["path"], str(pragma_h)) | |
| self.assertIn("#ifndef SRC_PRAGMA_H_", results[0]["replacements"][0]) | |
| self.assertEqual(results[1]["path"], str(broken_h)) | |
| self.assertIn("multiple #pragma once", results[1]["error"]) | |
| if __name__ == "__main__": | |
| unittest.main() |