def cmd_merge(args): """ Fetch a kernel repository, checkout particular references, and optionally apply patches from patchwork instances. Args: args: Command line arguments """ global retcode # Counter merge patch following: # idx[0]: counter of merge-ref option. # idx[1]: counter of patch option. # idx[2]: counter of pw option. idx = [0, 0, 0] # Clone the kernel tree and check out the proper ref. ktree = KernelTree( args.get('baserepo'), ref=args.get('ref'), wdir=full_path(args.get('workdir')), fetch_depth=args.get('fetch_depth') ) bhead = ktree.checkout() # Gather the subject and date of the commit that is currently checked out. bsubject = ktree.get_commit_subject(bhead) commitdate = ktree.get_commit_date(bhead) # Update the state file with what we know so far. state = { 'baserepo': args.get('baserepo'), 'basehead': bhead, 'basesubject': bsubject, 'commitdate': commitdate, 'workdir': full_path(args.get('workdir')), 'tag': get_tag(full_path(args.get('workdir'))), } update_state(args['rc'], state) # Loop over what we have been asked to merge (if applicable). for thing_to_merge in args.get('merge_queue', []): try: if thing_to_merge[0] == 'merge_ref': mbranch_ref = thing_to_merge[1].split() # Update the state file with our merge_ref data. state = { 'mergerepo_%02d' % idx[0]: mbranch_ref[0], 'mergehead_%02d' % idx[0]: bhead } update_state(args['rc'], state) # Merge the git ref. (retcode, bhead) = ktree.merge_git_ref(*mbranch_ref) if retcode: return # Increment the counter. idx[0] += 1 else: # Attempt to merge a local patch. if thing_to_merge[0] == 'patch': # Get the full path to the patch to merge. patch = os.path.abspath(thing_to_merge[1]) # Update the state file with our local patch data. state = {'localpatch_%02d' % idx[1]: patch} update_state(args['rc'], state) # Merge the patch. ktree.merge_patch_file(patch) # Increment the counter. idx[1] += 1 # Attempt to merge a patch from patchwork. elif thing_to_merge[0] == 'pw': patch = thing_to_merge[1] # Update the state file with our patchwork patch data. state = {'patchwork_%02d' % idx[2]: patch} update_state(args['rc'], state) # Merge the patch. Retrieve the Patchwork session cookie # first. session_id = get_state(args['rc'], 'patchwork_session_cookie') ktree.merge_patchwork_patch(patch, session_id) # Increment the counter. idx[2] += 1 # If the patch application failed, we should set the return code, # log an error, and update our state file. except PatchApplicationError as patch_exc: retcode = SKT_FAIL logging.error(patch_exc) # Update the state. state = {'mergelog': ktree.mergelog} update_state(args['rc'], state) return # If something else unexpected happened, re-raise the exception. except Exception: (exc, exc_type, trace) = sys.exc_info() raise exc, exc_type, trace # Get the SHA and subject of the repo after applying patches. buildhead = ktree.get_commit_hash() buildsubject = ktree.get_commit_subject() # Update the state file with the data about the current repo commit. state = { 'buildhead': buildhead, 'buildsubject': buildsubject } update_state(args['rc'], state)
def cmd_merge(cfg): """ Fetch a kernel repository, checkout particular references, and optionally apply patches from patchwork instances. Args: cfg: A dictionary of skt configuration. """ global retcode utypes = [] ktree = KernelTree(cfg.get('baserepo'), ref=cfg.get('ref'), wdir=cfg.get('workdir'), fetch_depth=cfg.get('fetch_depth')) bhead = ktree.checkout() commitdate = ktree.get_commit_date(bhead) save_state( cfg, { 'baserepo': cfg.get('baserepo'), 'basehead': bhead, 'commitdate': commitdate }) try: idx = 0 for mb in cfg.get('merge_ref'): save_state(cfg, { 'mergerepo_%02d' % idx: mb[0], 'mergehead_%02d' % idx: bhead }) (retcode, _) = ktree.merge_git_ref(*mb) utypes.append("[git]") idx += 1 if retcode: return if cfg.get('patchlist'): utypes.append("[local patch]") idx = 0 for patch in cfg.get('patchlist'): save_state(cfg, {'localpatch_%02d' % idx: patch}) ktree.merge_patch_file(os.path.abspath(patch)) idx += 1 if cfg.get('pw'): utypes.append("[patchwork]") idx = 0 for patch in cfg.get('pw'): save_state(cfg, {'patchwork_%02d' % idx: patch}) ktree.merge_patchwork_patch(patch) idx += 1 except Exception as e: save_state(cfg, {'mergelog': ktree.mergelog}) raise e uid = "[baseline]" if utypes: uid = " ".join(utypes) kpath = ktree.getpath() buildinfo = ktree.dumpinfo() buildhead = ktree.get_commit_hash() save_state( cfg, { 'workdir': kpath, 'buildinfo': buildinfo, 'buildhead': buildhead, 'uid': uid })
class KernelTreeTest(unittest.TestCase): # (Too many public methods) pylint: disable=too-many-public-methods """Test cases for KernelBuilder class.""" def setUp(self): """Fixtures for testing KernelTree.""" self.tmpdir = tempfile.mkdtemp() # Mock a successful subprocess.Popen self.m_popen_good = Mock() self.m_popen_good.returncode = 0 self.m_popen_good.communicate = Mock(return_value=('stdout', 'stderr')) self.popen_good = mock.patch('subprocess.Popen', Mock(return_value=self.m_popen_good)) # Mock an unsuccessful subprocess.Popen self.m_popen_bad = Mock() self.m_popen_bad.returncode = 1 self.m_popen_bad.communicate = Mock(return_value=('stdout', 'stderr')) self.popen_bad = mock.patch('subprocess.Popen', Mock(return_value=self.m_popen_bad)) self.kerneltree = KernelTree( uri=('git://git.kernel.org/pub/scm/linux/kernel/git/' 'stable/linux-stable.git'), ref='master', wdir=self.tmpdir, fetch_depth='1') def tearDown(self): """Teardown steps when testing is complete.""" # Some tests remove the work directory, so we should check for it # before deleting it. if os.path.isdir(self.tmpdir): shutil.rmtree(self.tmpdir) def test_cleanup(self): """Ensure cleanup() removes the workdir.""" ktree = self.kerneltree ktree.cleanup() def test_checkout(self): """Ensure checkout() runs git commands to check out a ref.""" self.m_popen_good.communicate = Mock(return_value=('stdout', None)) mock_git_cmd = mock.patch('skt.kerneltree.KernelTree.git_cmd') # Test with a fetch depth with self.popen_good, mock_git_cmd: result = self.kerneltree.checkout() self.assertEqual("stdout", result) # Test without a fetch depth self.kerneltree.fetch_depth = None with self.popen_good, mock_git_cmd: result = self.kerneltree.checkout() self.assertEqual("stdout", result) def test_dumpinfo(self): """Ensure dumpinfo() can dump data in a CSV format.""" self.kerneltree.info = [('test1', 'test2', 'test3')] result = self.kerneltree.dumpinfo() expected_filename = "{}/buildinfo.csv".format(self.tmpdir) # Ensure a file was made and its path was returned self.assertTrue(os.path.isfile(expected_filename)) self.assertEqual(result, expected_filename) with open(expected_filename, 'r') as fileh: file_contents = fileh.read() # Ensure the csv file has the correct data. self.assertEqual("test1,test2,test3\n", file_contents) def test_getpath(self): """Ensure that getpath() returns the workdir path.""" result = self.kerneltree.getpath() self.assertEqual(result, self.tmpdir) @mock.patch('subprocess.Popen') def test_get_commit_date(self, mock_popen): """Ensure that get_commit_date() returns an integer date.""" # Mock up an integer response that would normally come from the # 'git show' command mock_popen.return_value.communicate = Mock(return_value=('100', None)) # Test it with a ref result = self.kerneltree.get_commit_date(ref='master') call_args = mock_popen.call_args_list[0][0] self.assertIn('master', call_args[0]) mock_popen.reset_mock() self.assertEqual(result, 100) # Test it without a ref result = self.kerneltree.get_commit_date() call_args = mock_popen.call_args_list[0][0] self.assertNotIn('master', call_args[0]) self.assertEqual(result, 100) def test_get_commit_hash(self): """Ensure get_commit_hash() returns a git commit hash.""" self.m_popen_good.communicate = Mock(return_value=('abcdef', None)) with self.popen_good: result = self.kerneltree.get_commit_hash(ref='master') self.assertEqual(result, 'abcdef') def test_get_remote_url(self): """Ensure get_remote_url() returns a fetch url.""" expected_stdout = "Fetch URL: http://example.com/" self.m_popen_good.communicate = Mock(return_value=(expected_stdout, None)) with self.popen_good: result = self.kerneltree.get_remote_url('origin') self.assertEqual(result, 'http://example.com/') @mock.patch('logging.warning') def test_get_remote_name(self, mock_logging): """ Ensure get_remote_name() handles remote names from get_remote_url(). """ # If get_remote_url keeps returning the same value, then # get_remote_name() will keep adding underscores forever and this test # would never pass. mocked_get_remote_url = mock.patch( 'skt.kerneltree.KernelTree.get_remote_url', side_effect=['http://example.com/', "http://example2.com"]) with mocked_get_remote_url: result = self.kerneltree.get_remote_name("http://example.com/") self.assertEqual('example.com_', result) mock_logging.assert_called_once() @mock.patch('logging.debug') @mock.patch('subprocess.check_output') def test_git_cmd(self, mock_check_output, mock_logging): """Ensure git_cmd() works.""" mock_check_output.return_value = "Test return value" output = self.kerneltree.git_cmd("status") self.assertEqual("Test return value", output) mock_logging.assert_called_once() def test_merge_git_ref(self): """Ensure merge_git_ref() returns a proper tuple.""" mock_git_cmd = mock.patch('skt.kerneltree.KernelTree.git_cmd') mock_get_commit_hash = mock.patch( 'skt.kerneltree.KernelTree.get_commit_hash', return_value="abcdef") with mock_git_cmd, mock_get_commit_hash: result = self.kerneltree.merge_git_ref('http://example.com') self.assertTupleEqual((0, 'abcdef'), result) @mock.patch('logging.warning') @mock.patch('skt.kerneltree.KernelTree.get_remote_name') @mock.patch('skt.kerneltree.KernelTree.get_commit_hash') @mock.patch('skt.kerneltree.KernelTree.git_cmd') def test_merge_git_ref_failure(self, mock_git_cmd, mock_get_commit_hash, mock_get_remote_name, mock_logging): """Ensure merge_git_ref() fails properly when remote add fails.""" mock_get_remote_name.return_value = "origin" mock_git_cmd.side_effect = [ subprocess.CalledProcessError(1, 'That failed', "output"), True, subprocess.CalledProcessError(1, 'That failed', "output"), True ] mock_get_commit_hash.return_value = "abcdef" result = self.kerneltree.merge_git_ref('http://example.com') self.assertTupleEqual((1, None), result) mock_logging.assert_called_once() def test_merge_pw_patch(self): """Ensure merge_patchwork_patch() handles patches properly.""" mock_gpm = mock.patch('skt.get_patch_mbox') mock_git_cmd = mock.patch('skt.kerneltree.KernelTree.git_cmd') mock_gpn = mock.patch('skt.get_patch_name', return_value="patch_name") self.m_popen_good.communicate = Mock(return_value=('stdout', None)) self.m_popen_good.wait = Mock(return_value=0) with mock_gpm, mock_git_cmd, mock_gpn, self.popen_good: result = self.kerneltree.merge_patchwork_patch('uri') self.assertIsNone(result) self.assertTupleEqual(('patchwork', 'uri', 'patch_name'), self.kerneltree.info[0]) def test_merge_pw_patch_failure(self): """Ensure merge_patchwork_patch() handles patch failures properly.""" mock_get_patch_mbox = mock.patch('skt.get_patch_mbox') mock_git_cmd = mock.patch('skt.kerneltree.KernelTree.git_cmd') self.m_popen_bad.communicate = Mock(return_value=('stdout', None)) with mock_get_patch_mbox, mock_git_cmd, self.popen_bad: with self.assertRaises(Exception): self.kerneltree.merge_patchwork_patch('uri') def test_merge_patch_file(self): """Ensure merge_patch_file() tries to merge a patch.""" mock_check_output = mock.patch('subprocess.check_output', Mock(return_value='toot')) patch_file = "{}/test_patch.patch".format(self.tmpdir) with open(patch_file, 'w') as fileh: fileh.write('dummy patch data') with mock_check_output: self.kerneltree.merge_patch_file(patch_file) self.assertTupleEqual(('patch', patch_file), self.kerneltree.info[0]) def test_merge_patch_file_failure(self): """Ensure merge_patch_file() handles a patch apply failure.""" mock_check_output = mock.patch('subprocess.check_output', side_effect=make_process_exception) patch_file = "{}/test_patch.patch".format(self.tmpdir) with open(patch_file, 'w') as fileh: fileh.write('dummy patch data') with mock_check_output: with self.assertRaises(Exception): self.kerneltree.merge_patch_file(patch_file) def test_merge_patch_file_missing(self): """Ensure merge_patch_file() fails when a patch is missing.""" with self.assertRaises(Exception): self.kerneltree.merge_patch_file('patch_does_not_exist') @mock.patch('skt.kerneltree.KernelTree.git_cmd') def test_setup_repository_add(self, mock_git_cmd): """Ensure setup_repository() adds the origin URL.""" mock_git_cmd.side_effect = [True, "remote1\nremote2\remote3\n", True] self.kerneltree.setup_repository() self.assertIn('add', mock_git_cmd.call_args_list[2][0]) @mock.patch('skt.kerneltree.KernelTree.git_cmd') def test_setup_repository_set(self, mock_git_cmd): """Ensure setup_repository() sets the origin URL when it exists.""" mock_git_cmd.side_effect = [True, "remote1\nremote2\norigin\n", True] self.kerneltree.setup_repository() self.assertIn('set-url', mock_git_cmd.call_args_list[2][0])