| # -*- coding: utf-8 -*- |
| # Copyright 2016 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. |
| |
| """Module that contains unittests for patch_series module.""" |
| |
| from __future__ import print_function |
| |
| import contextlib |
| import os |
| import sys |
| |
| import mock |
| |
| from chromite.cbuildbot import patch_series |
| from chromite.lib import config_lib |
| from chromite.lib import gerrit |
| from chromite.lib import cros_test_lib |
| from chromite.lib import parallel_unittest |
| from chromite.lib import partial_mock |
| from chromite.lib import patch as cros_patch |
| from chromite.lib import patch_unittest |
| |
| |
| assert sys.version_info >= (3, 6), 'This module requires Python 3.6+' |
| |
| |
| class MockManifest(object): |
| """Helper class for Mocking Manifest objects.""" |
| |
| def __init__(self, path, **kwargs): |
| self.root = path |
| for key, attr in kwargs.items(): |
| setattr(self, key, attr) |
| |
| |
| def FakeFetchChangesForRepo(fetched_changes, by_repo, repo): |
| """Fake version of the "PatchSeries._FetchChangesForRepo" method. |
| |
| Thes does nothing to the changes and simply copies them into the output |
| dict. |
| """ |
| for c in by_repo[repo]: |
| fetched_changes[c.id] = c |
| |
| |
| class FakePatch(partial_mock.PartialMock): |
| """Mocks out dependency and fetch methods of GitRepoPatch. |
| |
| Examples: |
| set FakePatch.parents and .build_roots per patch, and set |
| FakePatch.assertEqual to your TestCase's assertEqual method. The behavior |
| of GerritDependencies, and Fetch` depends on the patch id. |
| """ |
| |
| TARGET = 'chromite.lib.patch.GitRepoPatch' |
| ATTRS = ('GerritDependencies', 'Fetch') |
| |
| parents = {} |
| build_root = None |
| assertEqual = None |
| |
| def PreStart(self): |
| FakePatch.parents = {} |
| |
| def PreStop(self): |
| FakePatch.build_root = None |
| FakePatch.assertEqual = None |
| |
| def GerritDependencies(self, patch): |
| return [cros_patch.ParsePatchDep(x) for x in self.parents[patch.id]] |
| |
| def Fetch(self, patch, path): |
| self._assertPath(patch, path) |
| return patch.sha1 |
| |
| def _assertPath(self, patch, path): |
| # pylint: disable=not-callable |
| self.assertEqual(path, |
| os.path.join(self.build_root, patch.project)) |
| |
| |
| class FakeGerritPatch(FakePatch): |
| """Mocks out the "GerritDependencies" method of GerritPatch. |
| |
| This is necessary because GerritPatch overrides the GerritDependencies method. |
| """ |
| TARGET = 'chromite.lib.patch.GerritPatch' |
| ATTRS = ('GerritDependencies',) |
| |
| |
| # pylint: disable=protected-access |
| # pylint: disable=too-many-ancestors |
| class PatchSeriesTestCase(patch_unittest.UploadedLocalPatchTestCase, |
| cros_test_lib.MockTestCase): |
| """Base class for tests that need to test PatchSeries.""" |
| |
| @contextlib.contextmanager |
| def _ValidateTransactionCall(self, _changes): |
| yield |
| |
| def setUp(self): |
| self.StartPatcher(parallel_unittest.ParallelMock()) |
| self._patch_factory = patch_unittest.MockPatchFactory() |
| self.build_root = 'fakebuildroot' |
| self.GetPatches = self._patch_factory.GetPatches |
| self.MockPatch = self._patch_factory.MockPatch |
| |
| def MakeHelper(self, cros_internal=None, cros=None): |
| # pylint: disable=attribute-defined-outside-init |
| site_params = config_lib.GetSiteParams() |
| if cros_internal: |
| cros_internal = mock.create_autospec(gerrit.GerritHelper) |
| cros_internal.version = '2.2' |
| cros_internal.remote = site_params.INTERNAL_REMOTE |
| if cros: |
| cros = mock.create_autospec(gerrit.GerritHelper) |
| cros.remote = site_params.EXTERNAL_REMOTE |
| cros.version = '2.2' |
| return patch_series.HelperPool(cros_internal=cros_internal, |
| cros=cros) |
| |
| def GetPatchSeries(self, helper_pool=None): |
| if helper_pool is None: |
| helper_pool = self.MakeHelper(cros_internal=True, cros=True) |
| series = patch_series.PatchSeries(self.build_root, helper_pool) |
| |
| # Suppress transactions. |
| series._Transaction = self._ValidateTransactionCall |
| series.GetGitRepoForChange = \ |
| lambda change, **kwargs: os.path.join(self.build_root, change.project) |
| |
| return series |
| |
| def CheckPatchApply(self, apply_mocks): |
| for apply_mock in apply_mocks: |
| apply_mock.assert_called_once_with(mock.ANY, trivial=False) |
| value = apply_mock.call_args[0][0] |
| self.assertTrue(isinstance(value, MockManifest)) |
| self.assertEqual(value.root, self.build_root) |
| |
| def SetPatchApply(self, patch): |
| return self.PatchObject(patch, 'ApplyAgainstManifest') |
| |
| def assertResults(self, series, changes, applied=(), failed_tot=(), |
| failed_inflight=(), frozen=True): |
| manifest = MockManifest(self.build_root) |
| result = series.Apply(changes, frozen=frozen, manifest=manifest) |
| |
| _GetIds = lambda seq: [x.id for x in seq] |
| _GetFailedIds = lambda seq: _GetIds(x.patch for x in seq) |
| |
| applied_result = _GetIds(result[0]) |
| failed_tot_result, failed_inflight_result = [ |
| _GetFailedIds(x) for x in result[1:]] |
| |
| applied = _GetIds(applied) |
| failed_tot = _GetIds(failed_tot) |
| failed_inflight = _GetIds(failed_inflight) |
| |
| self.assertEqual(applied, applied_result) |
| self.assertCountEqual(failed_inflight, failed_inflight_result) |
| self.assertCountEqual(failed_tot, failed_tot_result) |
| return result |
| |
| |
| class TestUploadedLocalPatch(PatchSeriesTestCase): |
| """Test the interaction between uploaded local git patches and PatchSeries.""" |
| |
| def testFetchChanges(self): |
| """Test fetching uploaded local patches.""" |
| git1, git2, patch1 = self._CommonGitSetup() |
| patch2 = self.CommitFile(git1, 'monkeys2', 'foon2') |
| patch3 = self._MkPatch(git1, None, original_sha1=patch1.sha1) |
| patch4 = self._MkPatch(git1, None, original_sha1=patch2.sha1) |
| self.assertEqual(patch3.id, patch1.id) |
| self.assertEqual(patch4.id, patch2.id) |
| self.assertNotEqual(patch3.id, patch4.id) |
| series = self.GetPatchSeries() |
| series.GetGitRepoForChange = lambda change, **kwargs: git2 |
| patches, _ = series.FetchChanges([patch3, patch4]) |
| self.assertEqual(len(patches), 2) |
| self.assertEqual(patches[0].id, patch3.id) |
| self.assertEqual(patches[1].id, patch4.id) |
| |
| def testFetchChangesWithChangeNotInManifest(self): |
| """test FetchChanges with ChangeNotInManifest.""" |
| # pylint: disable=unused-argument |
| def raiseException(change, **kwargs): |
| raise cros_patch.ChangeNotInManifest(change) |
| |
| patch_1, patch_2 = patches = self.GetPatches(2) |
| |
| series = self.GetPatchSeries() |
| series.GetGitRepoForChange = raiseException |
| |
| changes, not_in_manifest = series.FetchChanges(patches) |
| |
| self.assertEqual(len(changes), 0) |
| self.assertEqual(len(not_in_manifest), 2) |
| self.assertEqual(not_in_manifest[0].patch, patch_1) |
| self.assertEqual(not_in_manifest[1].patch, patch_2) |