aboutsummaryrefslogtreecommitdiff
path: root/llvm_tools
diff options
context:
space:
mode:
authorSalud Lemus <saludlemus@google.com>2019-07-25 12:04:56 -0700
committerSalud Lemus <saludlemus@google.com>2019-08-01 22:44:03 +0000
commit7abb7aa5c53f363434a9bcab633049323ddc96bc (patch)
tree13b9a95dcdac422e421f5877b47efe3cad037948 /llvm_tools
parent41b6b6f660aa2d43728edd0d0567ca5101eac4d1 (diff)
downloadtoolchain-utils-7abb7aa5c53f363434a9bcab633049323ddc96bc.tar.gz
LLVM tools: Unittests for patch_manager.py
BUG=None TEST='./patch_manager_unittest.py' passes Change-Id: I66235de2153bb32160d7ce301a86a246cef967a3 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/1719213 Tested-by: Salud Lemus <saludlemus@google.com> Reviewed-by: Manoj Gupta <manojgupta@chromium.org>
Diffstat (limited to 'llvm_tools')
-rwxr-xr-xllvm_tools/patch_manager_unittest.py804
1 files changed, 804 insertions, 0 deletions
diff --git a/llvm_tools/patch_manager_unittest.py b/llvm_tools/patch_manager_unittest.py
new file mode 100755
index 00000000..d32327d7
--- /dev/null
+++ b/llvm_tools/patch_manager_unittest.py
@@ -0,0 +1,804 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+# Copyright 2019 The Chromium OS Authors. All rights reserved.
+# 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."""
+
+from __future__ import print_function
+
+import json
+import mock
+import os
+import patch_manager
+import subprocess
+import unittest
+
+from failure_modes import FailureModes
+from test_helpers import CallCountsToMockFunctions
+from test_helpers import CreateTemporaryJsonFile
+from test_helpers import WritePrettyJsonFile
+
+
+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(os.path, 'isdir', return_value=False)
+ def testInvalidDirectoryPassedAsCommandLineArgument(self, mock_isdir):
+ # Verify the exception is raised when the command line argument for
+ # '--filesdir_path' or '--src_path' is not a directory.
+ with self.assertRaises(ValueError) as err:
+ patch_manager.is_directory('/some/path/that/is/not/a/directory')
+
+ self.assertEqual(
+ err.exception.message, 'Path is not a directory: '
+ '/some/path/that/is/not/a/directory')
+
+ mock_isdir.assert_called_once()
+
+ # Simulate the behavior of 'os.path.isdir()' when a path to a directory is
+ # passed as the command line argument for '--filesdir_path' or '--src_path'.
+ @mock.patch.object(os.path, 'isdir', return_value=True)
+ def testValidDirectoryPassedAsCommandLineArgument(self, mock_isdir):
+ self.assertEqual(
+ patch_manager.is_directory('/some/path/that/is/a/directory'),
+ '/some/path/that/is/a/directory')
+
+ mock_isdir.assert_called_once()
+
+ # Simulate behavior of 'os.path.isfile()' when the patch metadata file is does
+ # not exist.
+ @mock.patch.object(os.path, 'isfile', return_value=False)
+ def testInvalidPathToPatchMetadataFilePassedAsCommandLineArgument(
+ self, mock_isfile):
+
+ # Verify the exception is raised when the command line argument for
+ # '--patch_metadata_file' does not exist or is not a file.
+ with self.assertRaises(ValueError) as err:
+ patch_manager.is_patch_metadata_file('/abs/path/to/PATCHES.json')
+
+ self.assertEqual(
+ err.exception.message, 'Invalid patch metadata file provided: '
+ '/abs/path/to/PATCHES.json')
+
+ mock_isfile.assert_called_once()
+
+ # Simulate the behavior of 'os.path.isfile()' when the path to the patch
+ # metadata file exists and is a file.
+ @mock.patch.object(os.path, 'isfile', return_value=True)
+ def testPatchMetadataFileDoesNotEndInJson(self, mock_isfile):
+ # Verify the exception is raises when the command line argument for
+ # '--patch_metadata_file' exists and is a file but does not end in
+ # '.json'.
+ with self.assertRaises(ValueError) as err:
+ patch_manager.is_patch_metadata_file('/abs/path/to/PATCHES')
+
+ self.assertEqual(
+ err.exception.message, 'Patch metadata file does not end in \'.json\': '
+ '/abs/path/to/PATCHES')
+
+ mock_isfile.assert_called_once()
+
+ # Simulate the behavior of 'os.path.isfile()' when the command line argument
+ # for '--patch_metadata_file' exists and is a file.
+ @mock.patch.object(os.path, 'isfile', return_value=True)
+ def testValidPatchMetadataFilePassedAsCommandLineArgument(self, mock_isfile):
+ self.assertEqual(
+ patch_manager.is_patch_metadata_file('/abs/path/to/PATCHES.json'),
+ '/abs/path/to/PATCHES.json')
+
+ mock_isfile.assert_called_once()
+
+ # Simulate behavior of 'os.path.isdir()' when the path to $FILESDIR
+ # does not exist.
+ @mock.patch.object(os.path, 'isdir', return_value=False)
+ def testInvalidPathToFilesDirWhenConstructingPathToPatch(self, mock_isdir):
+ # Verify the exception is raised when the the absolute path to $FILESDIR of
+ # a package is not a directory.
+ with self.assertRaises(ValueError) as err:
+ patch_manager.GetPathToPatch('/abs/path/to/filesdir',
+ 'cherry/fixes_stdout.patch')
+
+ self.assertEqual(
+ err.exception.message, 'Invalid path to $FILESDIR provided: '
+ '/abs/path/to/filesdir')
+
+ mock_isdir.assert_called_once()
+
+ # Simulate behavior of 'os.path.isdir()' when the absolute path to the
+ # $FILESDIR of a package exists and is a directory.
+ @mock.patch.object(os.path, 'isdir', return_value=True)
+ # Simulate the behavior of 'os.path.isfile()' when the absolute path to the
+ # patch does not exist.
+ @mock.patch.object(os.path, 'isfile', return_value=False)
+ def testConstructedPathToPatchDoesNotExist(self, mock_isfile, mock_isdir):
+ # Verify the exception is raised when the absolute path to the patch does
+ # not exist.
+ with self.assertRaises(ValueError) as err:
+ patch_manager.GetPathToPatch('/abs/path/to/filesdir',
+ 'cherry/fixes_stdout.patch')
+
+ self.assertEqual(
+ err.exception.message, 'The absolute path /abs/path/to/filesdir/cherry/'
+ 'fixes_stdout.patch to the patch '
+ 'cherry/fixes_stdout.patch does not exist')
+
+ mock_isdir.assert_called_once()
+
+ mock_isfile.assert_called_once()
+
+ # Simulate behavior of 'os.path.isdir()' when the absolute path to the
+ # $FILESDIR of a package exists and is a directory.
+ @mock.patch.object(os.path, 'isdir', return_value=True)
+ # Simulate behavior of 'os.path.isfile()' when the absolute path to the
+ # patch exists and is a file.
+ @mock.patch.object(os.path, 'isfile', return_value=True)
+ def testConstructedPathToPatchSuccessfully(self, mock_isfile, mock_isdir):
+ self.assertEqual(
+ patch_manager.GetPathToPatch('/abs/path/to/filesdir',
+ 'cherry/fixes_stdout.patch'),
+ '/abs/path/to/filesdir/cherry/fixes_stdout.patch')
+
+ mock_isdir.assert_called_once()
+
+ mock_isfile.assert_called_once()
+
+ def testSuccessfullyGetPatchMetadataForPatchWithNoMetadata(self):
+ expected_patch_metadata = 0, None, False
+
+ test_patch = {
+ "comment": "Redirects output to stdout",
+ "rel_patch_path": "cherry/fixes_stdout.patch"
+ }
+
+ self.assertEqual(
+ patch_manager.GetPatchMetadata(test_patch), expected_patch_metadata)
+
+ def testSuccessfullyGetPatchMetdataForPatchWithSomeMetadata(self):
+ expected_patch_metadata = 0, 1000, False
+
+ test_patch = {
+ "comment": "Redirects output to stdout",
+ "rel_patch_path": "cherry/fixes_stdout.patch",
+ "end_version": 1000
+ }
+
+ self.assertEqual(
+ patch_manager.GetPatchMetadata(test_patch), expected_patch_metadata)
+
+ def testFailedToApplyPatchWhenInvalidSrcPathIsPassedIn(self):
+ # Verify the exception is raised when the absolute path to the unpacked
+ # sources of a package is not a directory.
+ with self.assertRaises(ValueError) as err:
+ patch_manager.ApplyPatch(
+ '/abs/path/to/src', '/abs/path/to/filesdir/cherry/fixes_stdout.patch')
+
+ self.assertEqual(err.exception.message,
+ 'Invalid src path provided: /abs/path/to/src')
+
+ # Simulate behavior of 'os.path.isdir()' when the absolute path to the
+ # unpacked sources of the package is valid and exists.
+ @mock.patch.object(os.path, 'isdir', return_value=True)
+ def testFailedToApplyPatchWhenPatchPathIsInvalid(self, mock_isdir):
+ # Verify the exception is raised when the absolute path to the patch does
+ # not exist or is not a file.
+ with self.assertRaises(ValueError) as err:
+ patch_manager.ApplyPatch(
+ '/abs/path/to/src', '/abs/path/to/filesdir/cherry/fixes_stdout.patch')
+
+ self.assertEqual(
+ err.exception.message, 'Invalid patch file provided: '
+ '/abs/path/to/filesdir/cherry/fixes_stdout.patch')
+
+ mock_isdir.assert_called_once()
+
+ # Simulate behavior of 'os.path.isdir()' when the absolute path to the
+ # unpacked sources of the package is valid and exists.
+ @mock.patch.object(os.path, 'isdir', return_value=True)
+ @mock.patch.object(os.path, 'isfile', return_value=True)
+ # Simulate behavior of 'os.path.isfile()' when the absolute path to the
+ # patch exists and is a file.
+ @mock.patch.object(subprocess, 'check_output')
+ def testFailedToApplyPatchInDryRun(self, mock_dry_run, mock_isfile,
+ mock_isdir):
+
+ # Simulate behavior of 'subprocess.check_output()' when '--dry-run'
+ # fails on the applying patch.
+ def FailedToApplyPatch(test_patch_cmd):
+ # First argument is the return error code, the second argument is the
+ # command that was run, and the third argument is the output.
+ raise subprocess.CalledProcessError(1, test_patch_cmd, None)
+
+ mock_dry_run.side_effect = FailedToApplyPatch
+
+ self.assertEqual(
+ patch_manager.ApplyPatch(
+ '/abs/path/to/src', '/abs/path/to/filesdir/cherry/'
+ 'fixes_stdout.patch'), False)
+
+ mock_isdir.assert_called_once()
+
+ mock_isfile.assert_called_once()
+
+ mock_dry_run.assert_called_once()
+
+ # Simulate behavior of 'os.path.isdir()' when the absolute path to the
+ # unpacked sources of the package is valid and exists.
+ @mock.patch.object(os.path, 'isdir', return_value=True)
+ @mock.patch.object(os.path, 'isfile', return_value=True)
+ # Simulate behavior of 'os.path.isfile()' when the absolute path to the
+ # patch exists and is a file.
+ @mock.patch.object(subprocess, 'check_output')
+ def testSuccessfullyAppliedPatch(self, mock_dry_run, mock_isfile, mock_isdir):
+
+ self.assertEqual(
+ patch_manager.ApplyPatch(
+ '/abs/path/to/src', '/abs/path/to/filesdir/cherry/'
+ 'fixes_stdout.patch'), True)
+
+ mock_isdir.assert_called_once()
+
+ mock_isfile.assert_called_once()
+
+ self.assertEqual(mock_dry_run.call_count, 2)
+
+ def testFailedToUpdatePatchMetadataFileWhenPatchFileNotEndInJson(self):
+ patch = [{
+ "comment": "Redirects output to stdout",
+ "rel_patch_path": "cherry/fixes_output.patch",
+ "start_version": 10
+ }]
+
+ # Verify the exception is raised when the absolute path to the patch
+ # metadata file does not end in '.json'.
+ with self.assertRaises(ValueError) as err:
+ patch_manager.UpdatePatchMetadataFile('/abs/path/to/filesdir/PATCHES',
+ patch)
+
+ self.assertEqual(
+ err.exception.message, 'File does not end in \'.json\': '
+ '/abs/path/to/filesdir/PATCHES')
+
+ def testSuccessfullyUpdatedPatchMetadataFile(self):
+ test_updated_patch_metadata = [{
+ "comment": "Redirects output to stdout",
+ "rel_patch_path": "cherry/fixes_output.patch",
+ "start_version": 10
+ }]
+
+ expected_patch_metadata = {
+ "comment": "Redirects output to stdout",
+ "rel_patch_path": "cherry/fixes_output.patch",
+ "start_version": 10
+ }
+
+ with CreateTemporaryJsonFile() as json_test_file:
+ patch_manager.UpdatePatchMetadataFile(json_test_file,
+ test_updated_patch_metadata)
+
+ # Make sure the updated patch metadata was written into the temporary
+ # .json file.
+ with open(json_test_file) as patch_file:
+ patch_contents = patch_manager._ConvertToASCII(json.load(patch_file))
+
+ self.assertEqual(len(patch_contents), 1)
+
+ self.assertDictEqual(patch_contents[0], expected_patch_metadata)
+
+ @mock.patch.object(patch_manager, 'GetPathToPatch')
+ def testExceptionThrownWhenHandlingPatches(self, mock_get_path_to_patch):
+ # Simulate behavior of 'GetPathToPatch()' when the absolute path to the
+ # patch does not exist.
+ def PathToPatchDoesNotExist(filesdir_path, rel_patch_path):
+ raise ValueError('The absolute path to /abs/path/to/filesdir/cherry/'
+ 'fix_output.patch does not exist')
+
+ # Use the test function to simulate the behavior of 'GetPathToPatch()'.
+ mock_get_path_to_patch.side_effect = PathToPatchDoesNotExist
+
+ test_patch_metadata = [{
+ "comment": "Redirects output to stdout",
+ "rel_patch_path": "cherry/fixes_output.patch",
+ "start_version": 10
+ }]
+
+ with CreateTemporaryJsonFile() as json_test_file:
+ # Write the test patch metadata to the temporary .json file.
+ with open(json_test_file, 'w') as json_file:
+ WritePrettyJsonFile(test_patch_metadata, json_file)
+
+ # Verify the exception is raised when the absolute path to a patch does
+ # not exist.
+ with self.assertRaises(ValueError) as err:
+ patch_manager.HandlePatches(1000, json_test_file,
+ '/abs/path/to/filesdir',
+ '/some/path/to/src', FailureModes.FAIL)
+
+ self.assertEqual(
+ err.exception.message,
+ 'The absolute path to /abs/path/to/filesdir/cherry/'
+ 'fix_output.patch does not exist')
+
+ mock_get_path_to_patch.assert_called_once_with('/abs/path/to/filesdir',
+ 'cherry/fixes_output.patch')
+
+ @mock.patch.object(patch_manager, 'GetPathToPatch')
+ # Simulate behavior for 'ApplyPatch()' when an applicable patch failed to
+ # apply.
+ @mock.patch.object(patch_manager, 'ApplyPatch', return_value=False)
+ def testExceptionThrownOnAFailedPatchInFailMode(self, mock_apply_patch,
+ mock_get_path_to_patch):
+ # Simulate behavior for 'GetPathToPatch()' when successfully constructed the
+ # absolute path to the patch and the patch exists.
+ mock_get_path_to_patch.return_value = ('/abs/path/to/filesdir/cherry/'
+ 'fixes_output.patch')
+
+ test_patch_metadata = [{
+ "comment": "Redirects output to stdout",
+ "rel_patch_path": "cherry/fixes_output.patch",
+ "start_version": 1000
+ }]
+
+ with CreateTemporaryJsonFile() as json_test_file:
+ # Write the test patch metadata to the temporary .json file.
+ with open(json_test_file, 'w') as json_file:
+ WritePrettyJsonFile(test_patch_metadata, json_file)
+
+ # Verify the exception is raised when the mode is 'fail' and an applicable
+ # patch fails to apply.
+ with self.assertRaises(ValueError) as err:
+ patch_manager.HandlePatches(1000, json_test_file,
+ '/abs/path/to/filesdir',
+ '/some/path/to/src', FailureModes.FAIL)
+
+ self.assertEqual(err.exception.message,
+ 'Failed to apply patch: fixes_output.patch')
+
+ mock_get_path_to_patch.assert_called_once_with('/abs/path/to/filesdir',
+ 'cherry/fixes_output.patch')
+
+ mock_apply_patch.assert_called_once_with(
+ '/some/path/to/src', '/abs/path/to/filesdir/cherry/'
+ 'fixes_output.patch')
+
+ @mock.patch.object(patch_manager, 'GetPathToPatch')
+ @mock.patch.object(patch_manager, 'ApplyPatch')
+ def testSomePatchesFailedToApplyInContinueMode(self, mock_apply_patch,
+ mock_get_path_to_patch):
+ # Simulate behavior for 'GetPathToPatch()' when successfully constructed the
+ # absolute path to the patch and the patch exists.
+ @CallCountsToMockFunctions
+ def MultipleCallsToGetPatchPath(call_count, filesdir_path, rel_patch_path):
+ # First patch to call 'GetPathToPatch()'.
+ if call_count == 0:
+ self.assertEqual(filesdir_path, '/abs/path/to/filesdir')
+ self.assertEqual(rel_patch_path, 'cherry/fixes_output.patch')
+
+ return '/abs/path/to/filesdir/cherry/fixes_output.patch'
+
+ # Second patch to call 'GetPathToPatch()'.
+ if call_count == 1:
+ self.assertEqual(filesdir_path, '/abs/path/to/filesdir')
+ self.assertEqual(rel_patch_path, 'cherry/fixes_input.patch')
+
+ return '/abs/path/to/filesdir/cherry/fixes_input.patch'
+
+ # Third patch to call 'GetPathToPatch()'.
+ if call_count == 2:
+ self.assertEqual(filesdir_path, '/abs/path/to/filesdir')
+ self.assertEqual(rel_patch_path, 'add_warning.patch')
+
+ return '/abs/path/to/filesdir/add_warning.patch'
+
+ # Fourth (and last) patch to call 'GetPathToPatch()'.
+ if call_count == 3:
+ self.assertEqual(filesdir_path, '/abs/path/to/filesdir')
+ self.assertEqual(rel_patch_path, 'add_helper.patch')
+
+ return '/abs/path/to/filesdir/add_helper.patch'
+
+ # 'GetPathToPatch()' was called more times than expected (4 times).
+ assert False, 'Unexpectedly called more than 4 times.'
+
+ # Simulate behavior for 'ApplyPatch()' when applying multiple applicable
+ # patches.
+ @CallCountsToMockFunctions
+ def MultipleCallsToApplyPatches(call_count, src_path, path_to_patch):
+ # First applicable patch that tries to apply patch.
+ if call_count == 0:
+ self.assertEqual(path_to_patch,
+ '/abs/path/to/filesdir/cherry/fixes_output.patch')
+
+ return True
+
+ # Second applicable patch that tries to apply patch.
+ if call_count == 1:
+ self.assertEqual(path_to_patch,
+ '/abs/path/to/filesdir/cherry/fixes_input.patch')
+
+ return False
+
+ # Third applicable patch that tries to apply patch.
+ if call_count == 2:
+ self.assertEqual(path_to_patch,
+ '/abs/path/to/filesdir/add_warning.patch')
+
+ return False
+
+ # 'ApplyPatch()' was called more times than expected (3 times).
+ assert False, 'Unexpectedly called more than 3 times.'
+
+ # Use test functions to simulate behavior.
+ mock_get_path_to_patch.side_effect = MultipleCallsToGetPatchPath
+ mock_apply_patch.side_effect = MultipleCallsToApplyPatches
+
+ test_patch_1 = {
+ "comment": "Redirects output to stdout",
+ "rel_patch_path": "cherry/fixes_output.patch",
+ "start_version": 1000,
+ "end_version": 1250
+ }
+
+ test_patch_2 = {
+ "comment": "Fixes input",
+ "rel_patch_path": "cherry/fixes_input.patch",
+ "start_version": 1000
+ }
+
+ test_patch_3 = {
+ "comment": "Adds a warning",
+ "rel_patch_path": "add_warning.patch",
+ "start_version": 750,
+ "end_version": 1500
+ }
+
+ test_patch_4 = {
+ "comment": "Adds a helper function",
+ "rel_patch_path": "add_helper.patch",
+ "start_version": 20,
+ "end_version": 900
+ }
+
+ test_patch_metadata = [
+ test_patch_1, test_patch_2, test_patch_3, test_patch_4
+ ]
+
+ expected_applied_patches = ['fixes_output.patch']
+ expected_failed_patches = ['fixes_input.patch', 'add_warning.patch']
+ expected_non_applicable_patches = ['add_helper.patch']
+
+ expected_patch_info_dict = {
+ 'applied_patches': expected_applied_patches,
+ 'failed_patches': expected_failed_patches,
+ 'non_applicable_patches': expected_non_applicable_patches,
+ 'disabled_patches': [],
+ 'removed_patches': [],
+ 'modified_metadata': None
+ }
+
+ with CreateTemporaryJsonFile() as json_test_file:
+ # Write the test patch metadata to the temporary .json file.
+ with open(json_test_file, 'w') as json_file:
+ WritePrettyJsonFile(test_patch_metadata, json_file)
+
+ patch_info = patch_manager.HandlePatches(
+ 1000, json_test_file, '/abs/path/to/filesdir', '/some/path/to/src',
+ FailureModes.CONTINUE)
+
+ self.assertDictEqual(patch_info._asdict(), expected_patch_info_dict)
+
+ self.assertEqual(mock_get_path_to_patch.call_count, 4)
+
+ self.assertEqual(mock_apply_patch.call_count, 3)
+
+ @mock.patch.object(patch_manager, 'GetPathToPatch')
+ @mock.patch.object(patch_manager, 'ApplyPatch')
+ def testSomePatchesAreDisabled(self, mock_apply_patch,
+ mock_get_path_to_patch):
+ # Simulate behavior for 'GetPathToPatch()' when successfully constructed the
+ # absolute path to the patch and the patch exists.
+ @CallCountsToMockFunctions
+ def MultipleCallsToGetPatchPath(call_count, filesdir_path, rel_patch_path):
+ # First patch to call 'GetPathToPatch()'.
+ if call_count == 0:
+ self.assertEqual(filesdir_path, '/abs/path/to/filesdir')
+ self.assertEqual(rel_patch_path, 'cherry/fixes_output.patch')
+
+ return '/abs/path/to/filesdir/cherry/fixes_output.patch'
+
+ # Second patch to call 'GetPathToPatch()'.
+ if call_count == 1:
+ self.assertEqual(filesdir_path, '/abs/path/to/filesdir')
+ self.assertEqual(rel_patch_path, 'cherry/fixes_input.patch')
+
+ return '/abs/path/to/filesdir/cherry/fixes_input.patch'
+
+ # Third patch to call 'GetPathToPatch()'.
+ if call_count == 2:
+ self.assertEqual(filesdir_path, '/abs/path/to/filesdir')
+ self.assertEqual(rel_patch_path, 'add_warning.patch')
+
+ return '/abs/path/to/filesdir/add_warning.patch'
+
+ # Fourth (and last) patch to call 'GetPathToPatch()'.
+ if call_count == 3:
+ self.assertEqual(filesdir_path, '/abs/path/to/filesdir')
+ self.assertEqual(rel_patch_path, 'add_helper.patch')
+
+ return '/abs/path/to/filesdir/add_helper.patch'
+
+ # 'GetPathToPatch()' was called more times than expected (4 times).
+ assert False, 'Unexpectedly called more than 4 times.'
+
+ # Simulate behavior for 'ApplyPatch()' when applying multiple applicable
+ # patches.
+ @CallCountsToMockFunctions
+ def MultipleCallsToApplyPatches(call_count, src_path, path_to_patch):
+ # First applicable patch that tries to apply patch.
+ if call_count == 0:
+ self.assertEqual(path_to_patch,
+ '/abs/path/to/filesdir/cherry/fixes_input.patch')
+
+ return False
+
+ # Second applicable patch that tries to apply patch.
+ if call_count == 1:
+ self.assertEqual(path_to_patch,
+ '/abs/path/to/filesdir/add_warning.patch')
+
+ return True
+
+ # Third applicable patch that tries to apply patch.
+ if call_count == 2:
+ self.assertEqual(path_to_patch,
+ '/abs/path/to/filesdir/add_helper.patch')
+
+ return False
+
+ # 'ApplyPatch()' was called more times than expected (3 times).
+ assert False, 'Unexpectedly called more than 3 times.'
+
+ # Use test functions to simulate behavior.
+ mock_get_path_to_patch.side_effect = MultipleCallsToGetPatchPath
+ mock_apply_patch.side_effect = MultipleCallsToApplyPatches
+
+ test_patch_1 = {
+ "comment": "Redirects output to stdout",
+ "rel_patch_path": "cherry/fixes_output.patch",
+ "start_version": 1000,
+ "end_version": 1190
+ }
+
+ test_patch_2 = {
+ "comment": "Fixes input",
+ "rel_patch_path": "cherry/fixes_input.patch",
+ "start_version": 1000
+ }
+
+ test_patch_3 = {
+ "comment": "Adds a warning",
+ "rel_patch_path": "add_warning.patch",
+ "start_version": 750,
+ "end_version": 1500
+ }
+
+ test_patch_4 = {
+ "comment": "Adds a helper function",
+ "rel_patch_path": "add_helper.patch",
+ "start_version": 20,
+ "end_version": 2000
+ }
+
+ test_patch_metadata = [
+ test_patch_1, test_patch_2, test_patch_3, test_patch_4
+ ]
+
+ expected_applied_patches = ['add_warning.patch']
+ expected_failed_patches = ['fixes_input.patch', 'add_helper.patch']
+ expected_disabled_patches = ['fixes_input.patch', 'add_helper.patch']
+ expected_non_applicable_patches = ['fixes_output.patch']
+
+ # Assigned 'None' for now, but it is expected that the patch metadata file
+ # will be modified, so the 'expected_patch_info_dict's' value for the
+ # key 'modified_metadata' will get updated to the temporary .json file once
+ # the file is created.
+ expected_modified_metadata_file = None
+
+ expected_patch_info_dict = {
+ 'applied_patches': expected_applied_patches,
+ 'failed_patches': expected_failed_patches,
+ 'non_applicable_patches': expected_non_applicable_patches,
+ 'disabled_patches': expected_disabled_patches,
+ 'removed_patches': [],
+ 'modified_metadata': expected_modified_metadata_file
+ }
+
+ with CreateTemporaryJsonFile() as json_test_file:
+ # Write the test patch metadata to the temporary .json file.
+ with open(json_test_file, 'w') as json_file:
+ WritePrettyJsonFile(test_patch_metadata, json_file)
+
+ expected_patch_info_dict['modified_metadata'] = json_test_file
+
+ patch_info = patch_manager.HandlePatches(
+ 1200, json_test_file, '/abs/path/to/filesdir', '/some/path/to/src',
+ FailureModes.DISABLE_PATCHES)
+
+ self.assertDictEqual(patch_info._asdict(), expected_patch_info_dict)
+
+ # 'test_patch_1' and 'test_patch_3' were not modified/disabled, so their
+ # dictionary is the same, but 'test_patch_2' and 'test_patch_4' were
+ # disabled, so their 'end_version' would be set to 1200, which was the
+ # value passed into 'HandlePatches()' for the 'svn_version'.
+ test_patch_2['end_version'] = 1200
+ test_patch_4['end_version'] = 1200
+
+ expected_json_file = [
+ test_patch_1, test_patch_2, test_patch_3, test_patch_4
+ ]
+
+ # Make sure the updated patch metadata was written into the temporary
+ # .json file.
+ with open(json_test_file) as patch_file:
+ new_json_file_contents = patch_manager._ConvertToASCII(
+ json.load(patch_file))
+
+ self.assertEqual(len(new_json_file_contents), 4)
+
+ for i in range(4):
+ self.assertDictEqual(new_json_file_contents[i], expected_json_file[i])
+
+ self.assertEqual(mock_get_path_to_patch.call_count, 4)
+
+ self.assertEqual(mock_apply_patch.call_count, 3)
+
+ @mock.patch.object(patch_manager, 'GetPathToPatch')
+ @mock.patch.object(patch_manager, 'ApplyPatch')
+ def testSomePatchesAreRemoved(self, mock_apply_patch, mock_get_path_to_patch):
+ # Simulate behavior for 'GetPathToPatch()' when successfully constructed the
+ # absolute path to the patch and the patch exists.
+ @CallCountsToMockFunctions
+ def MultipleCallsToGetPatchPath(call_count, filesdir_path, rel_patch_path):
+ # First patch to call 'GetPathToPatch()'.
+ if call_count == 0:
+ self.assertEqual(filesdir_path, '/abs/path/to/filesdir')
+ self.assertEqual(rel_patch_path, 'cherry/fixes_output.patch')
+
+ return '/abs/path/to/filesdir/cherry/fixes_output.patch'
+
+ # Second patch to call 'GetPathToPatch()'.
+ if call_count == 1:
+ self.assertEqual(filesdir_path, '/abs/path/to/filesdir')
+ self.assertEqual(rel_patch_path, 'cherry/fixes_input.patch')
+
+ return '/abs/path/to/filesdir/cherry/fixes_input.patch'
+
+ # Third patch to call 'GetPathToPatch()'.
+ if call_count == 2:
+ self.assertEqual(filesdir_path, '/abs/path/to/filesdir')
+ self.assertEqual(rel_patch_path, 'add_warning.patch')
+
+ return '/abs/path/to/filesdir/add_warning.patch'
+
+ # Fourth (and last) patch to call 'GetPathToPatch()'.
+ if call_count == 3:
+ self.assertEqual(filesdir_path, '/abs/path/to/filesdir')
+ self.assertEqual(rel_patch_path, 'add_helper.patch')
+
+ return '/abs/path/to/filesdir/add_helper.patch'
+
+ # 'GetPathToPatch()' was called more times than expected (4 times).
+ assert False, 'Unexpectedly called more than 4 times.'
+
+ # Use the test function to simulate behavior of 'GetPathToPatch()'.
+ mock_get_path_to_patch.side_effect = MultipleCallsToGetPatchPath
+
+ # For the 'remove_patches' mode, this patch is expected to be in the
+ # 'non_applicable_patches' list and 'removed_patches' list because
+ # the 'svn_version' (1500) >= 'end_version' (1190).
+ test_patch_1 = {
+ "comment": "Redirects output to stdout",
+ "rel_patch_path": "cherry/fixes_output.patch",
+ "start_version": 1000,
+ "end_version": 1190
+ }
+
+ # For the 'remove_patches' mode, this patch is expected to be in the
+ # 'applicable_patches' list (which is the list that the .json file will be
+ # updated with) because the 'svn_version' < 'inf' (this patch does not have
+ # an 'end_version' value which implies 'end_version' == 'inf').
+ test_patch_2 = {
+ "comment": "Fixes input",
+ "rel_patch_path": "cherry/fixes_input.patch",
+ "start_version": 1000
+ }
+
+ # For the 'remove_patches' mode, this patch is expected to be in the
+ # 'non_applicable_patches' list and 'removed_patches' list because
+ # the 'svn_version' (1500) >= 'end_version' (1500).
+ test_patch_3 = {
+ "comment": "Adds a warning",
+ "rel_patch_path": "add_warning.patch",
+ "start_version": 750,
+ "end_version": 1500
+ }
+
+ # For the 'remove_patches' mode, this patch is expected to be in the
+ # 'non_applicable_patches' list and 'removed_patches' list because
+ # the 'svn_version' (1500) >= 'end_version' (1400).
+ test_patch_4 = {
+ "comment": "Adds a helper function",
+ "rel_patch_path": "add_helper.patch",
+ "start_version": 20,
+ "end_version": 1400
+ }
+
+ test_patch_metadata = [
+ test_patch_1, test_patch_2, test_patch_3, test_patch_4
+ ]
+
+ expected_applied_patches = []
+ expected_failed_patches = []
+ expected_disabled_patches = []
+ expected_non_applicable_patches = [
+ 'fixes_output.patch', 'add_warning.patch', 'add_helper.patch'
+ ]
+ expected_removed_patches = [
+ '/abs/path/to/filesdir/cherry/fixes_output.patch',
+ '/abs/path/to/filesdir/add_warning.patch',
+ '/abs/path/to/filesdir/add_helper.patch'
+ ]
+
+ # Assigned 'None' for now, but it is expected that the patch metadata file
+ # will be modified, so the 'expected_patch_info_dict's' value for the
+ # key 'modified_metadata' will get updated to the temporary .json file once
+ # the file is created.
+ expected_modified_metadata_file = None
+
+ expected_patch_info_dict = {
+ 'applied_patches': expected_applied_patches,
+ 'failed_patches': expected_failed_patches,
+ 'non_applicable_patches': expected_non_applicable_patches,
+ 'disabled_patches': expected_disabled_patches,
+ 'removed_patches': expected_removed_patches,
+ 'modified_metadata': expected_modified_metadata_file
+ }
+
+ with CreateTemporaryJsonFile() as json_test_file:
+ # Write the test patch metadata to the temporary .json file.
+ with open(json_test_file, 'w') as json_file:
+ WritePrettyJsonFile(test_patch_metadata, json_file)
+
+ expected_patch_info_dict['modified_metadata'] = json_test_file
+
+ patch_info = patch_manager.HandlePatches(
+ 1500, json_test_file, '/abs/path/to/filesdir', '/some/path/to/src',
+ FailureModes.REMOVE_PATCHES)
+
+ self.assertDictEqual(patch_info._asdict(), expected_patch_info_dict)
+
+ # 'test_patch_2' was an applicable patch, so this patch will be the only
+ # patch that is in temporary .json file. The other patches were not
+ # applicable (they failed the applicable check), so they will not be in
+ # the .json file.
+ expected_json_file = [test_patch_2]
+
+ # Make sure the updated patch metadata was written into the temporary
+ # .json file.
+ with open(json_test_file) as patch_file:
+ new_json_file_contents = patch_manager._ConvertToASCII(
+ json.load(patch_file))
+
+ self.assertEqual(len(new_json_file_contents), 1)
+
+ self.assertDictEqual(new_json_file_contents[0], expected_json_file[0])
+
+ self.assertEqual(mock_get_path_to_patch.call_count, 4)
+
+ mock_apply_patch.assert_not_called()
+
+
+if __name__ == '__main__':
+ unittest.main()