Files
cdesouza-chromium 5f4207624a [brockit] Fixing dos-and-donts violations (#36416)
This PR corrects several of the dos-and-donts violations under
`tools/cr`. This is particularly useful to prevent future bugs with file
reads and writes that have been persistent from time to time.
2026-05-13 17:04:36 +01:00

346 lines
12 KiB
Python
Executable File

#!/usr/bin/env vpython3
# Copyright (c) 2026 The Brave Authors. All rights reserved.
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at https://mozilla.org/MPL/2.0/.
"""Integration tests for cmd.py.
All tests run cmd.py as a subprocess against a FakeChromiumRepo, which stands
in as the brave checkout that `repository.brave.root` resolves to. No
unittest.mock or monkeypatching is used for core logic; every test exercises
the actual Python + shell + git interaction.
Tests for the commit subcommand live in commit_test.py and import shared
helpers (_Sandbox, _run_gc, _GIT_ENV_OVERRIDES, CMD_SCRIPT, HOOK_SOURCE)
from this module.
"""
from __future__ import annotations
import os
import platform
import shutil
import stat
import subprocess
import sys
import unittest
from pathlib import Path
import _boot # noqa: F401
from test.fake_chromium_repo import FakeChromiumRepo
CMD_SCRIPT: Path = Path(__file__).parent / 'cmd.py'
HOOK_SOURCE: Path = Path(__file__).parent / 'commit-msg.py'
# Minimal git environment that suppresses GPG signing and user-config lookup.
_GIT_ENV_OVERRIDES: dict[str, str] = {
'GIT_AUTHOR_NAME': 'Test User',
'GIT_AUTHOR_EMAIL': 'test@brave.com',
'GIT_COMMITTER_NAME': 'Test User',
'GIT_COMMITTER_EMAIL': 'test@brave.com',
# Prevent any system/global hooks from interfering.
'GIT_CONFIG_NOSYSTEM': '1',
}
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _git(cwd: Path, *args: str) -> subprocess.CompletedProcess:
"""Run a git command in cwd; raise on failure."""
return subprocess.run(
['git'] + list(args),
cwd=cwd,
check=True,
capture_output=True,
text=True,
env={
**os.environ,
**_GIT_ENV_OVERRIDES
},
)
def _run_gc(
args: list[str],
*,
cwd: Path,
env: dict[str, str] | None = None,
stdin: str | None = None,
) -> subprocess.CompletedProcess:
"""Run git_cr.py as a subprocess and return the result."""
full_env = {**os.environ, **_GIT_ENV_OVERRIDES, **(env or {})}
return subprocess.run(
[sys.executable, str(CMD_SCRIPT)] + args,
cwd=cwd,
env=full_env,
capture_output=True,
text=True,
check=False,
input=stdin,
)
class _Sandbox:
"""A brave-named git checkout backed by FakeChromiumRepo.
`cmd.py` resolves brave-core via `repository.brave.root`, which walks up
from cwd to find a directory named 'brave'. Running cmd.py with
`cwd=self.root` makes it treat this fixture as the brave checkout.
"""
def __init__(self) -> None:
self._repo = FakeChromiumRepo()
def __enter__(self) -> '_Sandbox':
self._repo.setup()
_git(self.root, 'config', 'commit.gpgsign', 'false')
# Tests reference file.txt as the working file to stage and commit.
(self.root / 'file.txt').write_text('init\n', encoding='utf-8')
_git(self.root, 'add', 'file.txt')
_git(self.root, 'commit', '-m', 'Add file.txt')
return self
def __exit__(self, *_) -> None:
self._repo.cleanup()
@property
def root(self) -> Path:
return self._repo.brave
@property
def hook_dest(self) -> Path:
return self.root / '.git' / 'hooks' / 'commit-msg'
def install_hook(self, *, as_copy: bool = False) -> None:
"""Put commit-msg.py in .git/hooks/commit-msg.
By default a symlink is created (mirroring 'gc install-hook').
Pass as_copy=True to simulate a manually-copied (potentially
stale) hook.
"""
hooks_dir = self.root / '.git' / 'hooks'
hooks_dir.mkdir(exist_ok=True)
dest = self.hook_dest
if dest.exists() or dest.is_symlink():
dest.unlink()
if as_copy:
shutil.copy2(HOOK_SOURCE, dest)
else:
dest.symlink_to(HOOK_SOURCE)
# Ensure the hook (and source) are executable.
HOOK_SOURCE.chmod(HOOK_SOURCE.stat().st_mode | stat.S_IXUSR
| stat.S_IXGRP | stat.S_IXOTH)
dest.chmod(dest.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP
| stat.S_IXOTH)
def stage_change(self, content: str = 'change\n') -> None:
"""Modify file.txt and stage it so there is something to commit."""
(self.root / 'file.txt').write_text(content, encoding='utf-8')
_git(self.root, 'add', 'file.txt')
def last_commit_message(self) -> str:
"""Return the most recent commit message, stripped of trailing
whitespace."""
return subprocess.check_output(
['git', 'log', '-1', '--format=%B'],
cwd=self.root,
text=True,
env={
**os.environ,
**_GIT_ENV_OVERRIDES
},
).strip()
def run_gc(
self,
args: list[str],
*,
env: dict[str, str] | None = None,
stdin: str | None = None,
) -> subprocess.CompletedProcess:
"""Run git_cr.py from within this sandbox."""
return _run_gc(args, cwd=self.root, env=env, stdin=stdin)
def read_alias_cr(self) -> str:
"""Return the current alias.cr value from this sandbox's .git/config,
or ''."""
result = subprocess.run(
['git', 'config', '--local', 'alias.cr'],
cwd=self.root,
capture_output=True,
check=False,
text=True,
)
return result.stdout.strip()
# ---------------------------------------------------------------------------
# Helpers: fake global git config with core.hooksPath set
# ---------------------------------------------------------------------------
def _fake_global_env(hooks_dir: Path) -> dict[str, str]:
"""Return env overrides that point git's global config to a temp gitconfig
with core.hooksPath set to hooks_dir, without touching ~/.gitconfig."""
cfg = hooks_dir.parent / 'gitconfig'
cfg.write_text(f'[core]\n\thooksPath = {hooks_dir}\n', encoding='utf-8')
return {'GIT_CONFIG_GLOBAL': str(cfg)}
# ---------------------------------------------------------------------------
# Tests: core.hooksPath guard
# ---------------------------------------------------------------------------
class TestCoreHooksPathGuard(unittest.TestCase):
"""install-hook and commit must error when core.hooksPath redirects
hooks."""
def setUp(self) -> None:
self._sandbox = _Sandbox()
self._sandbox.__enter__()
self._fake_hooks = self._sandbox.root.parent / 'fake_hooks'
self._fake_hooks.mkdir()
self._env = _fake_global_env(self._fake_hooks)
def tearDown(self) -> None:
self._sandbox.__exit__(None, None, None)
def test_install_hook_blocked(self) -> None:
"""install-hook exits non-zero and mentions core.hooksPath."""
result = self._sandbox.run_gc(['install-hook'], env=self._env)
self.assertNotEqual(result.returncode, 0)
self.assertIn('core.hooksPath', result.stderr)
def test_install_hook_does_not_write_hook(self) -> None:
"""install-hook must not create the symlink when blocked."""
dest = self._sandbox.hook_dest
if dest.exists() or dest.is_symlink():
dest.unlink()
self._sandbox.run_gc(['install-hook'], env=self._env)
self.assertFalse(dest.exists() or dest.is_symlink())
def test_commit_blocked(self) -> None:
"""commit exits non-zero and mentions core.hooksPath."""
self._sandbox.stage_change()
result = self._sandbox.run_gc(['commit', '-m', 'msg'], env=self._env)
self.assertNotEqual(result.returncode, 0)
self.assertIn('core.hooksPath', result.stderr)
# ---------------------------------------------------------------------------
# Tests: install-hook
# ---------------------------------------------------------------------------
class TestInstallHook(unittest.TestCase):
"""git cr install-hook creates the correct symlink (or shim on Windows)."""
def setUp(self) -> None:
self._sandbox = _Sandbox()
self._sandbox.__enter__()
def tearDown(self) -> None:
self._sandbox.__exit__(None, None, None)
def _run(self) -> subprocess.CompletedProcess:
return self._sandbox.run_gc(['install-hook'])
def test_creates_symlink(self) -> None:
"""install-hook creates .git/hooks/commit-msg as a symlink on POSIX."""
if platform.system() == 'Windows':
self.skipTest('symlink test is POSIX-only')
result = self._run()
self.assertEqual(result.returncode, 0, msg=f'stderr: {result.stderr}')
dest = self._sandbox.hook_dest
self.assertTrue(dest.is_symlink(), 'Expected a symlink')
self.assertEqual(dest.resolve(), HOOK_SOURCE.resolve())
def test_overwrites_existing_hook(self) -> None:
"""install-hook replaces an existing hook file with a symlink."""
dest = self._sandbox.hook_dest
dest.parent.mkdir(exist_ok=True)
if dest.exists() or dest.is_symlink():
dest.unlink()
dest.write_text('old hook\n', encoding='utf-8')
result = self._run()
self.assertEqual(result.returncode, 0)
self.assertTrue(dest.is_symlink())
def test_hook_source_is_executable_after_install(self) -> None:
"""The hook source file is executable after install-hook runs."""
if platform.system() == 'Windows':
self.skipTest('executable bit is POSIX-only')
self._run()
self.assertTrue(HOOK_SOURCE.stat().st_mode & stat.S_IXUSR)
def test_output_mentions_paths(self) -> None:
"""install-hook prints both the destination and source paths."""
result = self._run()
self.assertIn('commit-msg', result.stdout)
self.assertEqual(result.returncode, 0)
# ---------------------------------------------------------------------------
# Tests: setup-alias
# ---------------------------------------------------------------------------
class TestSetupAlias(unittest.TestCase):
"""git cr setup-alias must register a working git alias in .git/config."""
def setUp(self) -> None:
self._sandbox = _Sandbox()
self._sandbox.__enter__()
self._sandbox.install_hook()
def tearDown(self) -> None:
self._sandbox.__exit__(None, None, None)
def _run(self) -> subprocess.CompletedProcess:
return self._sandbox.run_gc(['setup-alias'])
def test_writes_alias_to_git_config(self) -> None:
"""setup-alias stores alias.cr in .git/config."""
result = self._run()
self.assertEqual(result.returncode, 0, msg=f'stderr: {result.stderr}')
self.assertNotEqual(self._sandbox.read_alias_cr(), '')
def test_alias_references_script_and_vpython3(self) -> None:
"""The stored alias calls vpython3 with the cmd.py path."""
self._run()
alias = self._sandbox.read_alias_cr()
self.assertIn('vpython3', alias)
self.assertIn('cmd.py', alias)
def test_alias_starts_with_exclamation(self) -> None:
"""The alias uses the '!' prefix so git treats it as a shell command."""
self._run()
self.assertTrue(self._sandbox.read_alias_cr().startswith('!'))
def test_git_cr_commit_works_end_to_end(self) -> None:
"""After setup-alias, 'git cr -m msg' creates a commit in the sandbox.
"""
self._run()
self._sandbox.stage_change()
result = subprocess.run(
['git', 'cr', 'commit', '-m', 'Alias end-to-end'],
cwd=self._sandbox.root,
capture_output=True,
text=True,
check=False,
env={
**os.environ,
**_GIT_ENV_OVERRIDES
},
)
self.assertEqual(result.returncode, 0, msg=f'stderr: {result.stderr}')
self.assertIn('Alias end-to-end', self._sandbox.last_commit_message())
if __name__ == '__main__':
unittest.main()