#!/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()