| #!/usr/bin/env python3 |
| # Copyright 2019 The ChromiumOS Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Unit tests when handling patches.""" |
| |
| import json |
| from pathlib import Path |
| import tempfile |
| from typing import Callable |
| import unittest |
| from unittest import mock |
| |
| import atomic_write_file |
| import patch_manager |
| import patch_utils |
| |
| |
| class PatchManagerTest(unittest.TestCase): |
| """Test class when handling patches of packages.""" |
| |
| # Simulate behavior of 'os.path.isdir()' when the path is not a directory. |
| @mock.patch.object(Path, "is_dir", return_value=False) |
| def testInvalidDirectoryPassedAsCommandLineArgument(self, mock_isdir): |
| src_dir = "/some/path/that/is/not/a/directory" |
| patch_metadata_file = "/some/path/that/is/not/a/file" |
| |
| # Verify the exception is raised when the command line argument for |
| # '--filesdir_path' or '--src_path' is not a directory. |
| with self.assertRaises(ValueError): |
| patch_manager.main( |
| [ |
| "--src_path", |
| src_dir, |
| "--patch_metadata_file", |
| patch_metadata_file, |
| ] |
| ) |
| mock_isdir.assert_called_once() |
| |
| # Simulate behavior of 'os.path.isfile()' when the patch metadata file is |
| # does not exist. |
| @mock.patch.object(Path, "is_file", return_value=False) |
| def testInvalidPathToPatchMetadataFilePassedAsCommandLineArgument( |
| self, mock_isfile |
| ): |
| src_dir = "/some/path/that/is/not/a/directory" |
| patch_metadata_file = "/some/path/that/is/not/a/file" |
| |
| # Verify the exception is raised when the command line argument for |
| # '--filesdir_path' or '--src_path' is not a directory. |
| with mock.patch.object(Path, "is_dir", return_value=True): |
| with self.assertRaises(ValueError): |
| patch_manager.main( |
| [ |
| "--src_path", |
| src_dir, |
| "--patch_metadata_file", |
| patch_metadata_file, |
| ] |
| ) |
| mock_isfile.assert_called_once() |
| |
| @mock.patch("builtins.print") |
| @mock.patch.object(patch_utils, "git_clean_context") |
| def testCheckPatchApplies(self, _, mock_git_clean_context): |
| """Tests whether we can apply a single patch for a given svn_version.""" |
| mock_git_clean_context.return_value = mock.MagicMock() |
| with tempfile.TemporaryDirectory( |
| prefix="patch_manager_unittest" |
| ) as dirname: |
| dirpath = Path(dirname) |
| patch_entries = [ |
| patch_utils.PatchEntry( |
| dirpath, |
| metadata=None, |
| platforms=[], |
| rel_patch_path="another.patch", |
| version_range={ |
| "from": 9, |
| "until": 20, |
| }, |
| ), |
| patch_utils.PatchEntry( |
| dirpath, |
| metadata=None, |
| platforms=["chromiumos"], |
| rel_patch_path="example.patch", |
| version_range={ |
| "from": 1, |
| "until": 10, |
| }, |
| ), |
| patch_utils.PatchEntry( |
| dirpath, |
| metadata=None, |
| platforms=["chromiumos"], |
| rel_patch_path="patch_after.patch", |
| version_range={ |
| "from": 1, |
| "until": 5, |
| }, |
| ), |
| ] |
| patches_path = dirpath / "PATCHES.json" |
| with atomic_write_file.atomic_write( |
| patches_path, encoding="utf-8" |
| ) as f: |
| json.dump([pe.to_dict() for pe in patch_entries], f) |
| |
| def _harness1( |
| version: int, |
| return_value: patch_utils.PatchResult, |
| expected: patch_manager.GitBisectionCode, |
| ): |
| with mock.patch.object( |
| patch_utils.PatchEntry, |
| "apply", |
| return_value=return_value, |
| ) as m: |
| result = patch_manager.CheckPatchApplies( |
| version, |
| dirpath, |
| patches_path, |
| "example.patch", |
| ) |
| self.assertEqual(result, expected) |
| m.assert_called() |
| |
| _harness1( |
| 1, |
| patch_utils.PatchResult(True, {}), |
| patch_manager.GitBisectionCode.GOOD, |
| ) |
| _harness1( |
| 2, |
| patch_utils.PatchResult(True, {}), |
| patch_manager.GitBisectionCode.GOOD, |
| ) |
| _harness1( |
| 2, |
| patch_utils.PatchResult(False, {}), |
| patch_manager.GitBisectionCode.BAD, |
| ) |
| _harness1( |
| 11, |
| patch_utils.PatchResult(False, {}), |
| patch_manager.GitBisectionCode.BAD, |
| ) |
| |
| def _harness2( |
| version: int, |
| application_func: Callable, |
| expected: patch_manager.GitBisectionCode, |
| ): |
| with mock.patch.object( |
| patch_utils, |
| "apply_single_patch_entry", |
| application_func, |
| ): |
| result = patch_manager.CheckPatchApplies( |
| version, |
| dirpath, |
| patches_path, |
| "example.patch", |
| ) |
| self.assertEqual(result, expected) |
| |
| # Check patch can apply and fail with good return codes. |
| def _apply_patch_entry_mock1(v, _, patch_entry, _func, **__): |
| return patch_entry.can_patch_version(v), None |
| |
| _harness2( |
| 1, |
| _apply_patch_entry_mock1, |
| patch_manager.GitBisectionCode.GOOD, |
| ) |
| _harness2( |
| 11, |
| _apply_patch_entry_mock1, |
| patch_manager.GitBisectionCode.BAD, |
| ) |
| |
| # Early exit check, shouldn't apply later failing patch. |
| def _apply_patch_entry_mock2(v, _, patch_entry, _func, **__): |
| if ( |
| patch_entry.can_patch_version(v) |
| and patch_entry.rel_patch_path == "patch_after.patch" |
| ): |
| return False, {"filename": mock.Mock()} |
| return True, None |
| |
| _harness2( |
| 1, |
| _apply_patch_entry_mock2, |
| patch_manager.GitBisectionCode.GOOD, |
| ) |
| |
| # Skip check, should exit early on the first patch. |
| def _apply_patch_entry_mock3(v, _, patch_entry, _func, **__): |
| if ( |
| patch_entry.can_patch_version(v) |
| and patch_entry.rel_patch_path == "another.patch" |
| ): |
| return False, {"filename": mock.Mock()} |
| return True, None |
| |
| _harness2( |
| 9, |
| _apply_patch_entry_mock3, |
| patch_manager.GitBisectionCode.SKIP, |
| ) |
| |
| |
| if __name__ == "__main__": |
| unittest.main() |