#!/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 creating the arguments for the patch manager.""" from __future__ import print_function from collections import namedtuple import mock import os import unittest from cros_utils import command_executer from failure_modes import FailureModes from test_helpers import CallCountsToMockFunctions import llvm_patch_management import patch_manager class LlvmPatchManagementTest(unittest.TestCase): """Test class when constructing the arguments for the patch manager.""" def testInvalidChrootPathWhenGetPathToFilesDir(self): # Verify the exception is raised when an invalid absolute path to the chroot # is passed in. with self.assertRaises(ValueError) as err: llvm_patch_management.GetPathToFilesDirectory('/some/path/to/chroot', 'sys-devel/llvm') self.assertEqual(err.exception.message, 'Invalid chroot provided: /some/path/to/chroot') # Simulate the behavior of 'os.path.isdir()' when a valid chroot path is # passed in. @mock.patch.object(os.path, 'isdir', return_value=True) @mock.patch.object(command_executer.CommandExecuter, 'ChrootRunCommandWOutput') def testFailedToGetChrootPathToEbuildWhenGetPathToFilesDir( self, mock_chroot_cmd, mock_isdir): # Simulate behavior of 'ChrootRunCommandWOutput()' when failed to get the # absolute chroot path to the package's ebuild. # # Returns shell error code, stdout, stderr. mock_chroot_cmd.return_value = (1, None, 'Invalid package provided.') # Verify the exception is raised when failed to get the absolute chroot # path to a package's ebuild. with self.assertRaises(ValueError) as err: llvm_patch_management.GetPathToFilesDirectory('/some/path/to/chroot', 'test/package') self.assertEqual( err.exception.message, 'Failed to get the absolute chroot path of the package ' 'test/package: Invalid package provided.') mock_chroot_cmd.assert_called_once_with( chromeos_root='/some/path/to/chroot', command='equery w test/package', print_to_console=False) mock_isdir.assert_called_once() # Simulate the behavior of 'os.path.isdir()' when a valid chroot path is # passed in. @mock.patch.object(os.path, 'isdir', return_value=True) @mock.patch.object(command_executer.CommandExecuter, 'ChrootRunCommandWOutput') @mock.patch.object(llvm_patch_management, '_GetRelativePathOfChrootPath') def testSuccessfullyGetPathToFilesDir( self, mock_get_relative_path_of_chroot_path, mock_chroot_cmd, mock_isdir): # Simulate behavior of 'ChrootRunCommandWOutput()' when successfully # retrieved the absolute chroot path to the package's ebuild. # # Returns shell error code, stdout, stderr. mock_chroot_cmd.return_value = (0, '/mnt/host/source/path/to/llvm/llvm.ebuild', 0) # Simulate behavior of '_GetRelativePathOfChrootPath()' when successfully # removed '/mnt/host/source' of the absolute chroot path to the package's # ebuild. # # Returns relative path after '/mnt/host/source/'. mock_get_relative_path_of_chroot_path.return_value = 'path/to/llvm' self.assertEqual( llvm_patch_management.GetPathToFilesDirectory('/some/path/to/chroot', 'sys-devel/llvm'), '/some/path/to/chroot/path/to/llvm/files/') mock_isdir.assert_called_once() mock_chroot_cmd.assert_called_once() mock_get_relative_path_of_chroot_path.assert_called_once_with( '/mnt/host/source/path/to/llvm') def testInvalidPrefixForChrootPath(self): # Verify the exception is raised when the chroot path does not start with # '/mnt/host/source/'. with self.assertRaises(ValueError) as err: llvm_patch_management._GetRelativePathOfChrootPath('/path/to/llvm') self.assertEqual(err.exception.message, 'Invalid prefix for the chroot path: /path/to/llvm') def testValidPrefixForChrootPath(self): self.assertEqual( llvm_patch_management._GetRelativePathOfChrootPath( '/mnt/host/source/path/to/llvm'), 'path/to/llvm') # Simulate behavior of 'os.path.isfile()' when the patch metadata file does # not exist. @mock.patch.object(os.path, 'isfile', return_value=False) def testInvalidFileForPatchMetadataPath(self, mock_isfile): # Verify the exception is raised when the absolute path to the patch # metadata file does not exist. with self.assertRaises(ValueError) as err: llvm_patch_management._CheckPatchMetadataPath( '/abs/path/to/files/test.json') self.assertEqual(err.exception.message, 'Invalid file provided: /abs/path/to/files/test.json') mock_isfile.assert_called_once() # Simulate behavior of 'os.path.isfile()' when the absolute path to the # patch metadata file exists. @mock.patch.object(os.path, 'isfile', return_value=True) def testPatchMetadataFileDoesNotEndInJson(self, mock_isfile): # Verify the exception is raised when the patch metadata file does not end # in '.json'. with self.assertRaises(ValueError) as err: llvm_patch_management._CheckPatchMetadataPath( '/abs/path/to/files/PATCHES') self.assertEqual( err.exception.message, 'File does not end in \'.json\': ' '/abs/path/to/files/PATCHES') mock_isfile.assert_called_once() @mock.patch.object(os.path, 'isfile') def testValidPatchMetadataFile(self, mock_isfile): # Simulate behavior of 'os.path.isfile()' when the absolute path to the # patch metadata file exists. mock_isfile.return_value = True llvm_patch_management._CheckPatchMetadataPath( '/abs/path/to/files/PATCHES.json') mock_isfile.assert_called_once() @mock.patch.object(command_executer.CommandExecuter, 'ChrootRunCommandWOutput') def testFailedToUnpackPackage(self, mock_chroot_cmd): # Simulate the behavior of 'ChrootRunCommandWOutput()' when unpacking fails # on a package. @CallCountsToMockFunctions def MultipleCallsToGetSrcPath(call_count, chromeos_root, command, print_to_console, env=None): # First call to 'ChrootRunCommandWOutput()' which would successfully # get the ebuild path of the package. if call_count == 0: # Returns shell error code, stdout, stderr. return 0, '/mount/host/source/path/to/package/test-r1.ebuild', 0 # Second call to 'ChrootRunCommandWOutput()' which failed to unpack the # package. if call_count == 1: # Returns shell error code, stdout, stderr. return 1, None, 'Invalid package provided.' # 'ChrootRunCommandWOutput()' was called more times than expected (2 # times). assert False, ('Unexpectedly called more than 2 times.') # Use test function to simulate 'ChrootRunCommandWOutput()' behavior. mock_chroot_cmd.side_effect = MultipleCallsToGetSrcPath # Verify the exception is raised when failed to unpack a package. with self.assertRaises(ValueError) as err: llvm_patch_management.UnpackLLVMPackage('/some/path/to/chroot', 'test/package') self.assertEqual( err.exception.message, 'Failed to unpack the package test/package: ' 'Invalid package provided.') self.assertEqual(mock_chroot_cmd.call_count, 2) @mock.patch.object(command_executer.CommandExecuter, 'ChrootRunCommandWOutput') def testFailedToGetChrootPathToEbuild(self, mock_chroot_cmd): # Simulate the behavior of 'ChrootRunCommandWOutput()' when failed to get # the absolute chroot path to the package's ebuild. mock_chroot_cmd.return_value = (1, None, 'Invalid package provided.') # Verify the exception is raised when failed to get the absolute chroot # path to the package's ebuild. with self.assertRaises(ValueError) as err: llvm_patch_management.UnpackLLVMPackage('/some/path/to/chroot', 'test/package') self.assertEqual( err.exception.message, 'Failed to get the absolute chroot path to the ebuild of ' 'test/package: Invalid package provided.') mock_chroot_cmd.assert_called_once_with( chromeos_root='/some/path/to/chroot', command='equery w test/package', print_to_console=False) @mock.patch.object(command_executer.CommandExecuter, 'ChrootRunCommandWOutput') @mock.patch.object(llvm_patch_management, '_ConstructPathToSources') def testSuccessfullyGetSrcPath(self, mock_construct_src_path, mock_chroot_cmd): # Simulate the behavior of 'ChrootRunCommandWOutput()' when successfully # get the absolute chroot ebuild path to the package and successfully # unpacked the package. @CallCountsToMockFunctions def MultipleCallsToGetSrcPath(call_count, chromeos_root, command, print_to_console, env=None): # First call to 'ChrootRunCommandWOutput()' which would successfully # get the absolute chroot path to the package's ebuild. if call_count == 0: # Returns shell error code, stdout, stderr. return 0, '/mount/host/source/path/to/package/test-r1.ebuild', 0 # Second call to 'ChrootRunCommandWOutput()' which would successfully # unpack the package. if call_count == 1: # Returns shell error code, stdout, stderr. return 0, None, 0 # 'ChrootRunCommandWOutput()' was called more times than expected (2 # times). assert False, ('Unexpectedly called more than 2 times.') # Use the test function to simulate 'ChrootRunCommandWOutput()' behavior. mock_chroot_cmd.side_effect = MultipleCallsToGetSrcPath # Simulate the behavior of '_ConstructPathToSources()' when the ebuild name # has a revision number and '.ebuild' and the absolute path to the src # directory is valid. mock_construct_src_path.return_value = ('/some/path/to/chroot/chroot/var' '/tmp/portage/to/test-r1/work/' 'test') self.assertEqual( llvm_patch_management.UnpackLLVMPackage('/some/path/to/chroot', 'package/test'), '/some/path/to/chroot/chroot/var/tmp/portage/to/test-r1/work/test') self.assertEqual(mock_chroot_cmd.call_count, 2) mock_construct_src_path.assert_called_once_with('/some/path/to/chroot', 'test-r1.ebuild', 'to') def testFailedToRemoveEbuildPartFromTheEbuildName(self): # Verify the exception is raised when the ebuild name with the revision # number does not have '.ebuild' in the name. # # Ex: llvm-9.0_pre361749_p20190714-r4 # # Does not have a '.ebuild' in the ebuild name. with self.assertRaises(ValueError) as err: llvm_patch_management._ConstructPathToSources('/some/path/to/chroot', 'test-r1', 'test-packages') self.assertEqual(err.exception.message, 'Failed to remove \'.ebuild\' from test-r1.') def testFailedToRemoveTheRevisionNumberFromTheEbuildName(self): # Verify the exception is raised when the ebuild name with the revision # number does not have the revision number in the name. # # Ex: llvm-9.0_pre361749_p20190714.ebuild # # Does not have a revision number in the ebuild name. with self.assertRaises(ValueError) as err: llvm_patch_management._ConstructPathToSources( '/some/path/to/chroot', 'test.ebuild', 'test-packages') self.assertEqual(err.exception.message, 'Failed to remove the revision number from test.') # Simulate behavior of 'os.path.isdir()' when the constructed absolute path to # the unpacked sources does not exist. @mock.patch.object(os.path, 'isdir', return_value=False) def testInvalidPathToUnpackedSources(self, mock_isdir): # Verify the exception is raised when the absolute path to the unpacked # sources is constructed, but the path is invalid. with self.assertRaises(ValueError) as err: llvm_patch_management._ConstructPathToSources( '/some/path/to/chroot', 'test-r1.ebuild', 'test-packages') self.assertEqual( err.exception.message, 'Failed to construct the absolute path to the unpacked ' 'sources of the package test: ' '/some/path/to/chroot/chroot/var/tmp/portage/test-packages' '/test-r1/work/test') mock_isdir.assert_called_once() # Simulate the behavior of 'os.path.isdir()' when the absolute path to the # src directory exists. @mock.patch.object(os.path, 'isdir', return_value=True) def testSuccessfullyConstructedSrcPath(self, mock_isdir): self.assertEqual( llvm_patch_management._ConstructPathToSources( '/some/path/to/chroot', 'test-r1.ebuild', 'test-packages'), '/some/path/to/chroot/chroot/var/tmp/portage/test-packages/' 'test-r1/work/test') mock_isdir.assert_called_once() @mock.patch.object(llvm_patch_management, 'GetPathToFilesDirectory') @mock.patch.object(llvm_patch_management, '_CheckPatchMetadataPath') def testExceptionIsRaisedWhenUpdatingAPackagesMetadataFile( self, mock_check_patch_metadata_path, mock_get_filesdir_path): # Simulate the behavior of '_CheckPatchMetadataPath()' when the patch # metadata file in $FILESDIR does not exist or does not end in '.json'. def InvalidPatchMetadataFile(patch_metadata_path): self.assertEqual(patch_metadata_path, '/some/path/to/chroot/some/path/to/filesdir/PATCHES') raise ValueError('File does not end in \'.json\': ' '/some/path/to/chroot/some/path/to/filesdir/PATCHES') # Use the test function to simulate behavior of '_CheckPatchMetadataPath()'. mock_check_patch_metadata_path.side_effect = InvalidPatchMetadataFile # Simulate the behavior of 'GetPathToFilesDirectory()' when successfully # constructed the absolute path to $FILESDIR of a package. mock_get_filesdir_path.return_value = ('/some/path/to/chroot/some/path/' 'to/filesdir') # Verify the exception is raised when a package is constructing the # arguments for the patch manager to update its patch metadata file and an # exception is raised in the process. with self.assertRaises(ValueError) as err: llvm_patch_management.UpdatePackagesPatchMetadataFile( '/some/path/to/chroot', 1000, 'PATCHES', ['test-packages/package1'], FailureModes.FAIL) self.assertEqual( err.exception.message, 'File does not end in \'.json\': ' '/some/path/to/chroot/some/path/to/filesdir/PATCHES') mock_get_filesdir_path.assert_called_once_with('/some/path/to/chroot', 'test-packages/package1') mock_check_patch_metadata_path.assert_called_once() @mock.patch.object(llvm_patch_management, 'GetPathToFilesDirectory') @mock.patch.object(llvm_patch_management, '_CheckPatchMetadataPath') @mock.patch.object(llvm_patch_management, 'UnpackLLVMPackage') @mock.patch.object(patch_manager, 'HandlePatches') def testSuccessfullyRetrievedPatchResults( self, mock_handle_patches, mock_unpack_package, mock_check_patch_metadata_path, mock_get_filesdir_path): # Simulate the behavior of 'GetPathToFilesDirectory()' when successfully # constructed the absolute path to $FILESDIR of a package. mock_get_filesdir_path.return_value = ('/some/path/to/chroot/some/path/' 'to/filesdir') # Simulate the behavior of 'UnpackLLVMPackage()' when successfully unpacked # the package and constructed the absolute path to the unpacked sources. mock_unpack_package.return_value = ('/some/path/to/chroot/chroot/var/tmp/' 'portage/test-packages/package2-r1/work' '/package2') PatchInfo = namedtuple('PatchInfo', [ 'applied_patches', 'failed_patches', 'non_applicable_patches', 'disabled_patches', 'removed_patches', 'modified_metadata' ]) # Simulate the behavior of 'HandlePatches()' when successfully iterated # through every patch in the patch metadata file and a dictionary is # returned that contains information about the patches' status. mock_handle_patches.return_value = PatchInfo( applied_patches=['fixes_something.patch'], failed_patches=['disables_output.patch'], non_applicable_patches=[], disabled_patches=[], removed_patches=[], modified_metadata=None) expected_patch_results = { 'applied_patches': ['fixes_something.patch'], 'failed_patches': ['disables_output.patch'], 'non_applicable_patches': [], 'disabled_patches': [], 'removed_patches': [], 'modified_metadata': None } patch_info = llvm_patch_management.UpdatePackagesPatchMetadataFile( '/some/path/to/chroot', 1000, 'PATCHES.json', ['test-packages/package2'], FailureModes.CONTINUE) self.assertDictEqual(patch_info, {'test-packages/package2': expected_patch_results}) mock_get_filesdir_path.assert_called_once_with('/some/path/to/chroot', 'test-packages/package2') mock_check_patch_metadata_path.assert_called_once_with( '/some/path/to/chroot/some/path/to/filesdir/PATCHES.json') mock_unpack_package.assert_called_once_with('/some/path/to/chroot', 'test-packages/package2') mock_handle_patches.assert_called_once_with( 1000, '/some/path/to/chroot/some/path/to/filesdir/PATCHES.json', '/some/path/to/chroot/some/path/to/filesdir', '/some/path/to/chroot/chroot/var/tmp/portage/test-packages/' 'package2-r1/work/package2', FailureModes.CONTINUE) if __name__ == '__main__': unittest.main()