912 lines
34 KiB
Python
Executable File
912 lines
34 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# -*- 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 os
|
|
import subprocess
|
|
import unittest
|
|
import unittest.mock as mock
|
|
|
|
import patch_manager
|
|
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):
|
|
test_dir = '/some/path/that/is/not/a/directory'
|
|
|
|
# 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(test_dir)
|
|
|
|
self.assertEqual(
|
|
str(err.exception), 'Path is not a directory: '
|
|
'%s' % test_dir)
|
|
|
|
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):
|
|
test_dir = '/some/path/that/is/a/directory'
|
|
|
|
self.assertEqual(patch_manager.is_directory(test_dir), test_dir)
|
|
|
|
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):
|
|
|
|
abs_path_to_patch_file = '/abs/path/to/PATCHES.json'
|
|
|
|
# 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_patch_file)
|
|
|
|
self.assertEqual(
|
|
str(err.exception), 'Invalid patch metadata file provided: '
|
|
'%s' % abs_path_to_patch_file)
|
|
|
|
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):
|
|
abs_path_to_patch_file = '/abs/path/to/PATCHES'
|
|
|
|
# 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_patch_file)
|
|
|
|
self.assertEqual(
|
|
str(err.exception), 'Patch metadata file does not end in ".json": '
|
|
'%s' % abs_path_to_patch_file)
|
|
|
|
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):
|
|
abs_path_to_patch_file = '/abs/path/to/PATCHES.json'
|
|
|
|
self.assertEqual(
|
|
patch_manager.is_patch_metadata_file(abs_path_to_patch_file),
|
|
'%s' % abs_path_to_patch_file)
|
|
|
|
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):
|
|
abs_path_to_filesdir = '/abs/path/to/filesdir'
|
|
|
|
rel_patch_path = 'cherry/fixes_stdout.patch'
|
|
|
|
# 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, rel_patch_path)
|
|
|
|
self.assertEqual(
|
|
str(err.exception), 'Invalid path to $FILESDIR provided: '
|
|
'%s' % 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):
|
|
abs_path_to_filesdir = '/abs/path/to/filesdir'
|
|
|
|
rel_patch_path = 'cherry/fixes_stdout.patch'
|
|
|
|
abs_patch_path = os.path.join(abs_path_to_filesdir, rel_patch_path)
|
|
|
|
# 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, rel_patch_path)
|
|
|
|
self.assertEqual(
|
|
str(err.exception), 'The absolute path %s to the patch %s does not '
|
|
'exist' % (abs_patch_path, rel_patch_path))
|
|
|
|
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):
|
|
abs_path_to_filesdir = '/abs/path/to/filesdir'
|
|
|
|
rel_patch_path = 'cherry/fixes_stdout.patch'
|
|
|
|
abs_patch_path = os.path.join(abs_path_to_filesdir, rel_patch_path)
|
|
|
|
self.assertEqual(
|
|
patch_manager.GetPathToPatch(abs_path_to_filesdir, rel_patch_path),
|
|
abs_patch_path)
|
|
|
|
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):
|
|
src_path = '/abs/path/to/src'
|
|
|
|
abs_patch_path = '/abs/path/to/filesdir/cherry/fixes_stdout.patch'
|
|
|
|
# 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(src_path, abs_patch_path)
|
|
|
|
self.assertEqual(
|
|
str(err.exception), 'Invalid src path provided: %s' % src_path)
|
|
|
|
# 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):
|
|
src_path = '/abs/path/to/src'
|
|
|
|
abs_patch_path = '/abs/path/to/filesdir/cherry/fixes_stdout.patch'
|
|
|
|
# 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(src_path, abs_patch_path)
|
|
|
|
self.assertEqual(
|
|
str(err.exception), 'Invalid patch file provided: '
|
|
'%s' % abs_patch_path)
|
|
|
|
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(patch_manager, '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
|
|
|
|
src_path = '/abs/path/to/src'
|
|
|
|
abs_patch_path = '/abs/path/to/filesdir/cherry/fixes_stdout.patch'
|
|
|
|
self.assertEqual(patch_manager.ApplyPatch(src_path, abs_patch_path), 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(patch_manager, 'check_output')
|
|
def testSuccessfullyAppliedPatch(self, mock_dry_run, mock_isfile, mock_isdir):
|
|
src_path = '/abs/path/to/src'
|
|
|
|
abs_patch_path = '/abs/path/to/filesdir/cherry/fixes_stdout.patch'
|
|
|
|
self.assertEqual(patch_manager.ApplyPatch(src_path, abs_patch_path), 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
|
|
}]
|
|
|
|
abs_patch_path = '/abs/path/to/filesdir/PATCHES'
|
|
|
|
# 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_patch_path, patch)
|
|
|
|
self.assertEqual(
|
|
str(err.exception), 'File does not end in ".json": '
|
|
'%s' % abs_patch_path)
|
|
|
|
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 = 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):
|
|
filesdir_path = '/abs/path/to/filesdir'
|
|
|
|
abs_patch_path = '/abs/path/to/filesdir/cherry/fixes_output.patch'
|
|
|
|
rel_patch_path = 'cherry/fixes_output.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 %s does not exist' % os.path.join(
|
|
filesdir_path, rel_patch_path))
|
|
|
|
# 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': rel_patch_path,
|
|
'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)
|
|
|
|
src_path = '/some/path/to/src'
|
|
|
|
revision = 1000
|
|
|
|
# Verify the exception is raised when the absolute path to a patch does
|
|
# not exist.
|
|
with self.assertRaises(ValueError) as err:
|
|
patch_manager.HandlePatches(revision, json_test_file, filesdir_path,
|
|
src_path, FailureModes.FAIL)
|
|
|
|
self.assertEqual(
|
|
str(err.exception),
|
|
'The absolute path to %s does not exist' % abs_patch_path)
|
|
|
|
mock_get_path_to_patch.assert_called_once_with(filesdir_path,
|
|
rel_patch_path)
|
|
|
|
@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):
|
|
filesdir_path = '/abs/path/to/filesdir'
|
|
|
|
abs_patch_path = '/abs/path/to/filesdir/cherry/fixes_output.patch'
|
|
|
|
rel_patch_path = 'cherry/fixes_output.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_patch_path
|
|
|
|
test_patch_metadata = [{
|
|
'comment': 'Redirects output to stdout',
|
|
'rel_patch_path': rel_patch_path,
|
|
'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)
|
|
|
|
src_path = '/some/path/to/src'
|
|
|
|
revision = 1000
|
|
|
|
patch_name = 'fixes_output.patch'
|
|
|
|
# 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(revision, json_test_file, filesdir_path,
|
|
src_path, FailureModes.FAIL)
|
|
|
|
self.assertEqual(
|
|
str(err.exception), 'Failed to apply patch: %s' % patch_name)
|
|
|
|
mock_get_path_to_patch.assert_called_once_with(filesdir_path,
|
|
rel_patch_path)
|
|
|
|
mock_apply_patch.assert_called_once_with(src_path, abs_patch_path)
|
|
|
|
@mock.patch.object(patch_manager, 'GetPathToPatch')
|
|
@mock.patch.object(patch_manager, 'ApplyPatch')
|
|
def testSomePatchesFailedToApplyInContinueMode(self, mock_apply_patch,
|
|
mock_get_path_to_patch):
|
|
|
|
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
|
|
]
|
|
|
|
abs_path_to_filesdir = '/abs/path/to/filesdir'
|
|
|
|
# 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):
|
|
self.assertEqual(filesdir_path, abs_path_to_filesdir)
|
|
|
|
if call_count < 4:
|
|
self.assertEqual(rel_patch_path,
|
|
test_patch_metadata[call_count]['rel_patch_path'])
|
|
|
|
return os.path.join(abs_path_to_filesdir,
|
|
test_patch_metadata[call_count]['rel_patch_path'])
|
|
|
|
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):
|
|
if call_count < 3:
|
|
self.assertEqual(
|
|
path_to_patch,
|
|
os.path.join(abs_path_to_filesdir,
|
|
test_patch_metadata[call_count]['rel_patch_path']))
|
|
|
|
# Simulate that the first patch successfully applied.
|
|
return call_count == 0
|
|
|
|
# '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
|
|
|
|
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)
|
|
|
|
src_path = '/some/path/to/src/'
|
|
|
|
revision = 1000
|
|
|
|
patch_info = patch_manager.HandlePatches(revision, json_test_file,
|
|
abs_path_to_filesdir, src_path,
|
|
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):
|
|
|
|
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
|
|
]
|
|
|
|
abs_path_to_filesdir = '/abs/path/to/filesdir'
|
|
|
|
# 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):
|
|
self.assertEqual(filesdir_path, abs_path_to_filesdir)
|
|
|
|
if call_count < 4:
|
|
self.assertEqual(rel_patch_path,
|
|
test_patch_metadata[call_count]['rel_patch_path'])
|
|
|
|
return os.path.join(abs_path_to_filesdir,
|
|
test_patch_metadata[call_count]['rel_patch_path'])
|
|
|
|
# '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):
|
|
if call_count < 3:
|
|
self.assertEqual(
|
|
path_to_patch,
|
|
os.path.join(abs_path_to_filesdir,
|
|
test_patch_metadata[call_count + 1]['rel_patch_path']))
|
|
|
|
# Simulate that the second patch applied successfully.
|
|
return call_count == 1
|
|
|
|
# '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
|
|
|
|
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
|
|
|
|
src_path = '/some/path/to/src/'
|
|
|
|
revision = 1200
|
|
|
|
patch_info = patch_manager.HandlePatches(revision, json_test_file,
|
|
abs_path_to_filesdir, src_path,
|
|
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 = json.load(patch_file)
|
|
|
|
self.assertListEqual(new_json_file_contents, expected_json_file)
|
|
|
|
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):
|
|
# 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
|
|
]
|
|
|
|
abs_path_to_filesdir = '/abs/path/to/filesdir'
|
|
|
|
# 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):
|
|
self.assertEqual(filesdir_path, abs_path_to_filesdir)
|
|
|
|
if call_count < 4:
|
|
self.assertEqual(rel_patch_path,
|
|
test_patch_metadata[call_count]['rel_patch_path'])
|
|
|
|
return os.path.join(abs_path_to_filesdir,
|
|
test_patch_metadata[call_count]['rel_patch_path'])
|
|
|
|
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
|
|
|
|
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
|
|
|
|
abs_path_to_filesdir = '/abs/path/to/filesdir'
|
|
|
|
src_path = '/some/path/to/src/'
|
|
|
|
revision = 1500
|
|
|
|
patch_info = patch_manager.HandlePatches(revision, json_test_file,
|
|
abs_path_to_filesdir, src_path,
|
|
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 = json.load(patch_file)
|
|
|
|
self.assertListEqual(new_json_file_contents, expected_json_file)
|
|
|
|
self.assertEqual(mock_get_path_to_patch.call_count, 4)
|
|
|
|
mock_apply_patch.assert_not_called()
|
|
|
|
@mock.patch.object(patch_manager, 'GetPathToPatch')
|
|
@mock.patch.object(patch_manager, 'ApplyPatch')
|
|
def testSuccessfullyDidNotRemoveAFuturePatch(self, mock_apply_patch,
|
|
mock_get_path_to_patch):
|
|
|
|
# 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' (1200) >= '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
|
|
# 'applicable_patches' list because 'svn_version' >= 'start_version' and
|
|
# 'svn_version' < 'end_version'.
|
|
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
|
|
# 'applicable_patches' list because the patch is from the future (e.g.
|
|
# 'start_version' > 'svn_version' (1200), so it should NOT be removed.
|
|
test_patch_4 = {
|
|
'comment': 'Adds a helper function',
|
|
'rel_patch_path': 'add_helper.patch',
|
|
'start_version': 1600,
|
|
'end_version': 2000
|
|
}
|
|
|
|
test_patch_metadata = [
|
|
test_patch_1, test_patch_2, test_patch_3, test_patch_4
|
|
]
|
|
|
|
abs_path_to_filesdir = '/abs/path/to/filesdir'
|
|
|
|
# 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):
|
|
self.assertEqual(filesdir_path, abs_path_to_filesdir)
|
|
|
|
if call_count < 4:
|
|
self.assertEqual(rel_patch_path,
|
|
test_patch_metadata[call_count]['rel_patch_path'])
|
|
|
|
return os.path.join(abs_path_to_filesdir,
|
|
test_patch_metadata[call_count]['rel_patch_path'])
|
|
|
|
# '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
|
|
|
|
expected_applied_patches = []
|
|
expected_failed_patches = []
|
|
expected_disabled_patches = []
|
|
|
|
# 'add_helper.patch' is still a 'non applicable' patch meaning it does not
|
|
# apply in revision 1200 but it will NOT be removed because it is a future
|
|
# patch.
|
|
expected_non_applicable_patches = ['fixes_output.patch', 'add_helper.patch']
|
|
expected_removed_patches = [
|
|
'/abs/path/to/filesdir/cherry/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': 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
|
|
|
|
src_path = '/some/path/to/src/'
|
|
|
|
revision = 1200
|
|
|
|
patch_info = patch_manager.HandlePatches(revision, json_test_file,
|
|
abs_path_to_filesdir, src_path,
|
|
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, 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 = json.load(patch_file)
|
|
|
|
self.assertListEqual(new_json_file_contents, expected_json_file)
|
|
|
|
self.assertEqual(mock_get_path_to_patch.call_count, 4)
|
|
|
|
mock_apply_patch.assert_not_called()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|