diff options
Diffstat (limited to 'llvm_tools/get_patch_unittest.py')
-rwxr-xr-x | llvm_tools/get_patch_unittest.py | 233 |
1 files changed, 233 insertions, 0 deletions
diff --git a/llvm_tools/get_patch_unittest.py b/llvm_tools/get_patch_unittest.py new file mode 100755 index 00000000..8ccdb69b --- /dev/null +++ b/llvm_tools/get_patch_unittest.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 +# Copyright 2024 The ChromiumOS Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Tests for get_patch.""" + +import json +from pathlib import Path +import tempfile +from typing import Any, Dict, Generator, List, Set +import unittest +from unittest import mock + +import get_patch +import git_llvm_rev + + +COMMIT_FIXTURES: List[Dict[str, Any]] = [ + {"subject": "A commit subject", "sha": "abcdef1234567890", "rev": 5}, + {"subject": "Another commit subject", "sha": "feed9999", "rev": 9}, +] + +JSON_FIXTURE: List[Dict[str, Any]] = [ + { + "metadata": {"title": "An existing patch"}, + "platforms": ["another platform"], + "rel_patch_path": "cherry/nowhere.patch", + "version_range": {"from": 1, "until": 256}, + }, +] + + +def _mock_get_commit_subj(_, sha: str) -> str: + gen: Generator[Dict[str, Any], None, None] = ( + fixture for fixture in COMMIT_FIXTURES if fixture["sha"] == sha + ) + return next(gen)["subject"] + + +def _mock_to_rev(sha: get_patch.LLVMGitRef, _) -> git_llvm_rev.Rev: + gen: Generator[Dict[str, Any], None, None] = ( + fixture for fixture in COMMIT_FIXTURES if fixture["sha"] == sha.git_ref + ) + return git_llvm_rev.Rev("main", next(gen)["rev"]) + + +def _mock_from_rev(_, rev: git_llvm_rev.Rev) -> get_patch.LLVMGitRef: + gen: Generator[Dict[str, Any], None, None] = ( + fixture for fixture in COMMIT_FIXTURES if fixture["rev"] == rev.number + ) + return get_patch.LLVMGitRef(next(gen)["sha"]) + + +def _mock_git_format_patch(*_) -> str: + return "[category] This is a fake commit fixture" + + +def _mock_write_patch(*_) -> None: + return + + +def _mock_get_changed_packages(*_) -> Set[Path]: + return {get_patch.LLVM_PKG_PATH} + + +class TestGetPatch(unittest.TestCase): + """Test case harness for get_patch.""" + + def setUp(self) -> None: + """Set up the mocks and directory structure.""" + + self.module_patcher = mock.patch.multiple( + "get_patch", + get_commit_subj=_mock_get_commit_subj, + git_format_patch=_mock_git_format_patch, + get_changed_packages=_mock_get_changed_packages, + _write_patch=_mock_write_patch, + ) + self.module_patcher.start() + self.addCleanup(self.module_patcher.stop) + self.llvm_gitsha_patcher = mock.patch.multiple( + "get_patch.LLVMGitRef", + to_rev=_mock_to_rev, + from_rev=_mock_from_rev, + ) + self.llvm_gitsha_patcher.start() + self.addCleanup(self.llvm_gitsha_patcher.stop) + + self.llvm_project_dir = Path(tempfile.mkdtemp()) + self.addCleanup(self.llvm_project_dir.rmdir) + self.chromiumos_root = Path(tempfile.mkdtemp()) + self.addCleanup(self.chromiumos_root.rmdir) + self.workdir = self.chromiumos_root / get_patch.LLVM_PKG_PATH / "files" + self.workdir.mkdir(parents=True, exist_ok=True) + + def _cleanup_workdir(): + # We individually clean up these directories as a guarantee + # we aren't creating any extraneous files. We don't want to + # use shm.rmtree here because we don't want clean up any + # files unaccounted for. + workdir_recurse = self.workdir + while workdir_recurse not in (self.chromiumos_root, Path.root): + workdir_recurse.rmdir() + workdir_recurse = workdir_recurse.parent + + self.addCleanup(_cleanup_workdir) + + self.patches_json_file = ( + self.workdir / get_patch.PATCH_METADATA_FILENAME + ) + start_ref = get_patch.LLVMGitRef("abcdef1234567890") + self.ctx = get_patch.PatchContext( + self.llvm_project_dir, + self.chromiumos_root, + start_ref, + platforms=["some platform"], + ) + + def write_json_fixture(self) -> None: + with self.patches_json_file.open("w", encoding="utf-8") as f: + json.dump(JSON_FIXTURE, f) + f.write("\n") + + def test_bad_cherrypick_version(self) -> None: + """Test that bad cherrypick versions raises.""" + start_sha_fixture = COMMIT_FIXTURES[0] + + def _try_make_patches(): + # This fixture is the same as the start_sha. + self.ctx.make_patches( + get_patch.LLVMGitRef(start_sha_fixture["sha"]) + ) + + self.assertRaises(get_patch.CherrypickVersionError, _try_make_patches) + + def test_make_patches(self) -> None: + """Test we can make patch entries from a git commit.""" + + fixture = COMMIT_FIXTURES[1] + # We manually write and delete this file because it must have the name + # as specified by get_patch. tempfile cannot guarantee us this name. + self.write_json_fixture() + try: + entries = self.ctx.make_patches( + get_patch.LLVMGitRef(fixture["sha"]) + ) + self.assertEqual(len(entries), 1) + if entries[0].metadata: + self.assertEqual( + entries[0].metadata["title"], fixture["subject"] + ) + else: + self.fail("metadata was None") + finally: + self.patches_json_file.unlink() + + def test_apply_patch_to_json(self) -> None: + """Test we can apply patches to the JSON file.""" + + fixture = COMMIT_FIXTURES[1] + fixture_sha = fixture["sha"] + expected_json_entry = { + "metadata": {"title": fixture["subject"], "info": []}, + "platforms": ["some platform"], + "rel_patch_path": f"cherry/{fixture_sha}.patch", + "version_range": { + "from": self.ctx.start_ref.to_rev(self.llvm_project_dir).number, + "until": fixture["rev"], + }, + } + cherrydir = self.workdir / "cherry" + cherrydir.mkdir() + self._apply_patch_to_json_helper(fixture, expected_json_entry) + cherrydir.rmdir() + + def test_apply_patch_to_json_no_cherry(self) -> None: + """Test we can apply patches to the JSON file, without a cherry dir.""" + + fixture = COMMIT_FIXTURES[1] + fixture_sha = fixture["sha"] + expected_json_entry = { + "metadata": {"title": fixture["subject"], "info": []}, + "platforms": ["some platform"], + "rel_patch_path": f"{fixture_sha}.patch", + "version_range": { + "from": self.ctx.start_ref.to_rev(self.llvm_project_dir).number, + "until": fixture["rev"], + }, + } + self._apply_patch_to_json_helper(fixture, expected_json_entry) + + def _apply_patch_to_json_helper(self, fixture, expected_json_entry) -> None: + # We manually write and delete this file because it must have the name + # as specified by get_patch. tempfile cannot guarantee us this name. + self.write_json_fixture() + patch_source = get_patch.LLVMGitRef.from_rev( + self.llvm_project_dir, + git_llvm_rev.Rev("origin", fixture["rev"]), + ) + try: + self.ctx.apply_patches(patch_source) + with self.patches_json_file.open(encoding="utf-8") as f: + edited = json.load(f) + self.assertEqual(edited, JSON_FIXTURE + [expected_json_entry]) + finally: + self.patches_json_file.unlink() + + def test_apply_patch_dry_run(self) -> None: + """Test dry running patches does nothing.""" + + fixture = COMMIT_FIXTURES[1] + old_dry_run = self.ctx.dry_run + self.ctx.dry_run = True + # We manually write and delete this file because it must have the name + # as specified by get_patch. tempfile cannot guarantee us this name. + self.write_json_fixture() + patch_source = get_patch.LLVMGitRef.from_rev( + self.llvm_project_dir, + git_llvm_rev.Rev("origin", fixture["rev"]), + ) + try: + self.ctx.apply_patches(patch_source) + with self.patches_json_file.open(encoding="utf-8") as f: + maybe_edited = json.load(f) + self.assertEqual(maybe_edited, JSON_FIXTURE) + finally: + self.ctx.dry_run = old_dry_run + self.patches_json_file.unlink() + + +if __name__ == "__main__": + unittest.main() |