| # Copyright 2016 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style |
| # license that can be found in the LICENSE file or at |
| # https://developers.google.com/open-source/licenses/bsd |
| |
| """Tests for the frontendsearchpipeline module.""" |
| from __future__ import print_function |
| from __future__ import division |
| from __future__ import absolute_import |
| |
| try: |
| from mox3 import mox |
| except ImportError: |
| import mox |
| import unittest |
| |
| from google.appengine.api import memcache |
| from google.appengine.api import modules |
| from google.appengine.ext import testbed |
| from google.appengine.api import urlfetch |
| |
| import settings |
| from framework import framework_helpers |
| from framework import sorting |
| from framework import urls |
| from proto import ast_pb2 |
| from proto import project_pb2 |
| from proto import tracker_pb2 |
| from search import frontendsearchpipeline |
| from search import searchpipeline |
| from search import query2ast |
| from services import service_manager |
| from testing import fake |
| from testing import testing_helpers |
| from tracker import tracker_bizobj |
| |
| |
| # Just an example timestamp. The value does not matter. |
| NOW = 2444950132 |
| |
| |
| class FrontendSearchPipelineTest(unittest.TestCase): |
| |
| def setUp(self): |
| self.config = tracker_bizobj.MakeDefaultProjectIssueConfig(789) |
| self.services = service_manager.Services( |
| user=fake.UserService(), |
| project=fake.ProjectService(), |
| issue=fake.IssueService(), |
| config=fake.ConfigService(), |
| cache_manager=fake.CacheManager()) |
| self.services.user.TestAddUser('a@example.com', 111) |
| self.project = self.services.project.TestAddProject('proj', project_id=789) |
| self.mr = testing_helpers.MakeMonorailRequest( |
| path='/p/proj/issues/list', project=self.project) |
| self.mr.me_user_id = 111 |
| |
| self.issue_1 = fake.MakeTestIssue( |
| 789, 1, 'one', 'New', 111, labels=['Priority-High']) |
| self.services.issue.TestAddIssue(self.issue_1) |
| self.issue_2 = fake.MakeTestIssue( |
| 789, 2, 'two', 'New', 111, labels=['Priority-Low']) |
| self.services.issue.TestAddIssue(self.issue_2) |
| self.issue_3 = fake.MakeTestIssue( |
| 789, 3, 'three', 'New', 111, labels=['Priority-Medium']) |
| self.services.issue.TestAddIssue(self.issue_3) |
| self.mr.sort_spec = 'Priority' |
| |
| self.cnxn = self.mr.cnxn |
| self.project = self.mr.project |
| self.auth = self.mr.auth |
| self.me_user_id = self.mr.me_user_id |
| self.query = self.mr.query |
| self.query_project_names = self.mr.query_project_names |
| self.items_per_page = self.mr.num # defaults to 100 |
| self.paginate_start = self.mr.start |
| self.paginate_end = self.paginate_start + self.items_per_page |
| self.can = self.mr.can |
| self.group_by_spec = self.mr.group_by_spec |
| self.sort_spec = self.mr.sort_spec |
| self.warnings = self.mr.warnings |
| self.errors = self.mr.errors |
| self.use_cached_searches = self.mr.use_cached_searches |
| self.profiler = self.mr.profiler |
| |
| self.mox = mox.Mox() |
| self.testbed = testbed.Testbed() |
| self.testbed.activate() |
| self.testbed.init_user_stub() |
| self.testbed.init_memcache_stub() |
| sorting.InitializeArtValues(self.services) |
| |
| def tearDown(self): |
| self.testbed.deactivate() |
| self.mox.UnsetStubs() |
| self.mox.ResetAll() |
| |
| def testSearchForIIDs_AllResultsCached_AllAtRiskCached(self): |
| unfiltered_iids = {(1, 'p:v'): [1001, 1011]} |
| nonviewable_iids = {1: set()} |
| self.mox.StubOutWithMock(frontendsearchpipeline, '_StartBackendSearch') |
| frontendsearchpipeline._StartBackendSearch( |
| self.cnxn, ['proj'], [789], mox.IsA(tracker_pb2.ProjectIssueConfig), |
| unfiltered_iids, {}, nonviewable_iids, set(), self.services, |
| self.me_user_id, self.auth.user_id or 0, self.paginate_end, |
| self.query.split(' OR '), self.can, self.group_by_spec, self.sort_spec, |
| self.warnings, self.use_cached_searches).AndReturn([]) |
| self.mox.StubOutWithMock(frontendsearchpipeline, '_FinishBackendSearch') |
| frontendsearchpipeline._FinishBackendSearch([]) |
| self.mox.ReplayAll() |
| |
| pipeline = frontendsearchpipeline.FrontendSearchPipeline( |
| self.cnxn, |
| self.services, |
| self.auth, |
| self.me_user_id, |
| self.query, |
| self.query_project_names, |
| self.items_per_page, |
| self.paginate_start, |
| self.can, |
| self.group_by_spec, |
| self.sort_spec, |
| self.warnings, |
| self.errors, |
| self.use_cached_searches, |
| self.profiler, |
| project=self.project) |
| pipeline.unfiltered_iids = unfiltered_iids |
| pipeline.nonviewable_iids = nonviewable_iids |
| pipeline.SearchForIIDs() |
| self.mox.VerifyAll() |
| self.assertEqual(2, pipeline.total_count) |
| self.assertEqual([1001, 1011], pipeline.filtered_iids[(1, 'p:v')]) |
| |
| def testSearchForIIDs_CrossProject_AllViewable(self): |
| self.services.project.TestAddProject('other', project_id=790) |
| unfiltered_iids = {(1, 'p:v'): [1001, 1011, 2001]} |
| nonviewable_iids = {1: set()} |
| self.query_project_names = ['other'] |
| self.mox.StubOutWithMock(frontendsearchpipeline, '_StartBackendSearch') |
| frontendsearchpipeline._StartBackendSearch( |
| self.cnxn, ['other', 'proj'], [789, 790], |
| mox.IsA(tracker_pb2.ProjectIssueConfig), unfiltered_iids, {}, |
| nonviewable_iids, set(), self.services, |
| self.me_user_id, self.auth.user_id or 0, self.paginate_end, |
| self.query.split(' OR '), self.can, self.group_by_spec, self.sort_spec, |
| self.warnings, self.use_cached_searches).AndReturn([]) |
| self.mox.StubOutWithMock(frontendsearchpipeline, '_FinishBackendSearch') |
| frontendsearchpipeline._FinishBackendSearch([]) |
| self.mox.ReplayAll() |
| |
| pipeline = frontendsearchpipeline.FrontendSearchPipeline( |
| self.cnxn, |
| self.services, |
| self.auth, |
| self.me_user_id, |
| self.query, |
| self.query_project_names, |
| self.items_per_page, |
| self.paginate_start, |
| self.can, |
| self.group_by_spec, |
| self.sort_spec, |
| self.warnings, |
| self.errors, |
| self.use_cached_searches, |
| self.profiler, |
| project=self.project) |
| |
| pipeline.unfiltered_iids = unfiltered_iids |
| pipeline.nonviewable_iids = nonviewable_iids |
| pipeline.SearchForIIDs() |
| self.mox.VerifyAll() |
| self.assertEqual(3, pipeline.total_count) |
| self.assertEqual([1001, 1011, 2001], pipeline.filtered_iids[(1, 'p:v')]) |
| |
| def testSearchForIIDs_CrossProject_MembersOnlyOmitted(self): |
| self.services.project.TestAddProject( |
| 'other', project_id=790, access=project_pb2.ProjectAccess.MEMBERS_ONLY) |
| unfiltered_iids = {(1, 'p:v'): [1001, 1011]} |
| nonviewable_iids = {1: set()} |
| # project 'other' gets filtered out before the backend call. |
| self.mr.query_project_names = ['other'] |
| self.mox.StubOutWithMock(frontendsearchpipeline, '_StartBackendSearch') |
| frontendsearchpipeline._StartBackendSearch( |
| self.cnxn, ['proj'], [789], mox.IsA(tracker_pb2.ProjectIssueConfig), |
| unfiltered_iids, {}, nonviewable_iids, set(), self.services, |
| self.me_user_id, self.auth.user_id or 0, self.paginate_end, |
| self.query.split(' OR '), self.can, self.group_by_spec, self.sort_spec, |
| self.warnings, self.use_cached_searches).AndReturn([]) |
| self.mox.StubOutWithMock(frontendsearchpipeline, '_FinishBackendSearch') |
| frontendsearchpipeline._FinishBackendSearch([]) |
| self.mox.ReplayAll() |
| |
| pipeline = frontendsearchpipeline.FrontendSearchPipeline( |
| self.cnxn, |
| self.services, |
| self.auth, |
| self.me_user_id, |
| self.query, |
| self.query_project_names, |
| self.items_per_page, |
| self.paginate_start, |
| self.can, |
| self.group_by_spec, |
| self.sort_spec, |
| self.warnings, |
| self.errors, |
| self.use_cached_searches, |
| self.profiler, |
| project=self.project) |
| pipeline.unfiltered_iids = unfiltered_iids |
| pipeline.nonviewable_iids = nonviewable_iids |
| pipeline.SearchForIIDs() |
| self.mox.VerifyAll() |
| self.assertEqual(2, pipeline.total_count) |
| self.assertEqual([1001, 1011], pipeline.filtered_iids[(1, 'p:v')]) |
| |
| def testMergeAndSortIssues_EmptyResult(self): |
| pipeline = frontendsearchpipeline.FrontendSearchPipeline( |
| self.cnxn, |
| self.services, |
| self.auth, |
| self.me_user_id, |
| self.query, |
| self.query_project_names, |
| self.items_per_page, |
| self.paginate_start, |
| self.can, |
| self.group_by_spec, |
| self.sort_spec, |
| self.warnings, |
| self.errors, |
| self.use_cached_searches, |
| self.profiler, |
| project=self.project) |
| pipeline.filtered_iids = {0: [], 1: [], 2: []} |
| |
| pipeline.MergeAndSortIssues() |
| self.assertEqual([], pipeline.allowed_iids) |
| self.assertEqual([], pipeline.allowed_results) |
| self.assertEqual({}, pipeline.users_by_id) |
| |
| def testMergeAndSortIssues_Normal(self): |
| pipeline = frontendsearchpipeline.FrontendSearchPipeline( |
| self.cnxn, |
| self.services, |
| self.auth, |
| self.me_user_id, |
| self.query, |
| self.query_project_names, |
| self.items_per_page, |
| self.paginate_start, |
| self.can, |
| self.group_by_spec, |
| self.sort_spec, |
| self.warnings, |
| self.errors, |
| self.use_cached_searches, |
| self.profiler, |
| project=self.project) |
| # In this unit test case we are not calling SearchForIIDs(), instead just |
| # set pipeline.filtered_iids directly. |
| pipeline.filtered_iids = { |
| 0: [], |
| 1: [self.issue_1.issue_id], |
| 2: [self.issue_2.issue_id], |
| 3: [self.issue_3.issue_id] |
| } |
| |
| pipeline.MergeAndSortIssues() |
| self.assertEqual( |
| [self.issue_1.issue_id, self.issue_2.issue_id, self.issue_3.issue_id], |
| pipeline.allowed_iids) |
| self.assertEqual( |
| [self.issue_1, self.issue_3, self.issue_2], # high, medium, low. |
| pipeline.allowed_results) |
| self.assertEqual([0, 111], list(pipeline.users_by_id.keys())) |
| |
| def testDetermineIssuePosition_Normal(self): |
| pipeline = frontendsearchpipeline.FrontendSearchPipeline( |
| self.cnxn, |
| self.services, |
| self.auth, |
| self.me_user_id, |
| self.query, |
| self.query_project_names, |
| self.items_per_page, |
| self.paginate_start, |
| self.can, |
| self.group_by_spec, |
| self.sort_spec, |
| self.warnings, |
| self.errors, |
| self.use_cached_searches, |
| self.profiler, |
| project=self.project) |
| # In this unit test case we are not calling SearchForIIDs(), instead just |
| # set pipeline.filtered_iids directly. |
| pipeline.filtered_iids = { |
| 0: [], |
| 1: [self.issue_1.issue_id], |
| 2: [self.issue_2.issue_id], |
| 3: [self.issue_3.issue_id] |
| } |
| |
| prev_iid, index, next_iid = pipeline.DetermineIssuePosition(self.issue_3) |
| # The total ordering is issue_1, issue_3, issue_2 for high, med, low. |
| self.assertEqual(self.issue_1.issue_id, prev_iid) |
| self.assertEqual(1, index) |
| self.assertEqual(self.issue_2.issue_id, next_iid) |
| |
| def testDetermineIssuePosition_NotInResults(self): |
| pipeline = frontendsearchpipeline.FrontendSearchPipeline( |
| self.cnxn, |
| self.services, |
| self.auth, |
| self.me_user_id, |
| self.query, |
| self.query_project_names, |
| self.items_per_page, |
| self.paginate_start, |
| self.can, |
| self.group_by_spec, |
| self.sort_spec, |
| self.warnings, |
| self.errors, |
| self.use_cached_searches, |
| self.profiler, |
| project=self.project) |
| # In this unit test case we are not calling SearchForIIDs(), instead just |
| # set pipeline.filtered_iids directly. |
| pipeline.filtered_iids = { |
| 0: [], |
| 1: [self.issue_1.issue_id], |
| 2: [self.issue_2.issue_id], |
| 3: [] |
| } |
| |
| prev_iid, index, next_iid = pipeline.DetermineIssuePosition(self.issue_3) |
| # The total ordering is issue_1, issue_3, issue_2 for high, med, low. |
| self.assertEqual(None, prev_iid) |
| self.assertEqual(None, index) |
| self.assertEqual(None, next_iid) |
| |
| def testDetermineIssuePositionInShard_IssueIsInShard(self): |
| pipeline = frontendsearchpipeline.FrontendSearchPipeline( |
| self.cnxn, |
| self.services, |
| self.auth, |
| self.me_user_id, |
| self.query, |
| self.query_project_names, |
| self.items_per_page, |
| self.paginate_start, |
| self.can, |
| self.group_by_spec, |
| self.sort_spec, |
| self.warnings, |
| self.errors, |
| self.use_cached_searches, |
| self.profiler, |
| project=self.project) |
| # Let's assume issues 1, 2, and 3 are all in the same shard. |
| pipeline.filtered_iids = { |
| 0: [self.issue_1.issue_id, self.issue_2.issue_id, self.issue_3.issue_id], |
| } |
| |
| # The total ordering is issue_1, issue_3, issue_2 for high, med, low. |
| prev_cand, index, next_cand = pipeline._DetermineIssuePositionInShard( |
| 0, self.issue_1, {}) |
| self.assertEqual(None, prev_cand) |
| self.assertEqual(0, index) |
| self.assertEqual(self.issue_3, next_cand) |
| |
| prev_cand, index, next_cand = pipeline._DetermineIssuePositionInShard( |
| 0, self.issue_3, {}) |
| self.assertEqual(self.issue_1, prev_cand) |
| self.assertEqual(1, index) |
| self.assertEqual(self.issue_2, next_cand) |
| |
| prev_cand, index, next_cand = pipeline._DetermineIssuePositionInShard( |
| 0, self.issue_2, {}) |
| self.assertEqual(self.issue_3, prev_cand) |
| self.assertEqual(2, index) |
| self.assertEqual(None, next_cand) |
| |
| def testDetermineIssuePositionInShard_IssueIsNotInShard(self): |
| pipeline = frontendsearchpipeline.FrontendSearchPipeline( |
| self.cnxn, |
| self.services, |
| self.auth, |
| self.me_user_id, |
| self.query, |
| self.query_project_names, |
| self.items_per_page, |
| self.paginate_start, |
| self.can, |
| self.group_by_spec, |
| self.sort_spec, |
| self.warnings, |
| self.errors, |
| self.use_cached_searches, |
| self.profiler, |
| project=self.project) |
| |
| # The total ordering is issue_1, issue_3, issue_2 for high, med, low. |
| pipeline.filtered_iids = { |
| 0: [self.issue_2.issue_id, self.issue_3.issue_id], |
| } |
| prev_cand, index, next_cand = pipeline._DetermineIssuePositionInShard( |
| 0, self.issue_1, {}) |
| self.assertEqual(None, prev_cand) |
| self.assertEqual(0, index) |
| self.assertEqual(self.issue_3, next_cand) |
| |
| pipeline.filtered_iids = { |
| 0: [self.issue_1.issue_id, self.issue_2.issue_id], |
| } |
| prev_cand, index, next_cand = pipeline._DetermineIssuePositionInShard( |
| 0, self.issue_3, {}) |
| self.assertEqual(self.issue_1, prev_cand) |
| self.assertEqual(1, index) |
| self.assertEqual(self.issue_2, next_cand) |
| |
| pipeline.filtered_iids = { |
| 0: [self.issue_1.issue_id, self.issue_3.issue_id], |
| } |
| prev_cand, index, next_cand = pipeline._DetermineIssuePositionInShard( |
| 0, self.issue_2, {}) |
| self.assertEqual(self.issue_3, prev_cand) |
| self.assertEqual(2, index) |
| self.assertEqual(None, next_cand) |
| |
| def testFetchAllSamples_Empty(self): |
| filtered_iids = {} |
| pipeline = frontendsearchpipeline.FrontendSearchPipeline( |
| self.cnxn, |
| self.services, |
| self.auth, |
| self.me_user_id, |
| self.query, |
| self.query_project_names, |
| self.items_per_page, |
| self.paginate_start, |
| self.can, |
| self.group_by_spec, |
| self.sort_spec, |
| self.warnings, |
| self.errors, |
| self.use_cached_searches, |
| self.profiler, |
| project=self.project) |
| samples_by_shard, sample_iids_to_shard = pipeline._FetchAllSamples( |
| filtered_iids) |
| self.assertEqual({}, samples_by_shard) |
| self.assertEqual({}, sample_iids_to_shard) |
| |
| def testFetchAllSamples_SmallResultsPerShard(self): |
| filtered_iids = { |
| 0: [100, 110, 120], |
| 1: [101, 111, 121], |
| } |
| pipeline = frontendsearchpipeline.FrontendSearchPipeline( |
| self.cnxn, |
| self.services, |
| self.auth, |
| self.me_user_id, |
| self.query, |
| self.query_project_names, |
| self.items_per_page, |
| self.paginate_start, |
| self.can, |
| self.group_by_spec, |
| self.sort_spec, |
| self.warnings, |
| self.errors, |
| self.use_cached_searches, |
| self.profiler, |
| project=self.project) |
| |
| samples_by_shard, sample_iids_to_shard = pipeline._FetchAllSamples( |
| filtered_iids) |
| self.assertEqual(2, len(samples_by_shard)) |
| self.assertEqual(0, len(sample_iids_to_shard)) |
| |
| def testFetchAllSamples_Normal(self): |
| pipeline = frontendsearchpipeline.FrontendSearchPipeline( |
| self.cnxn, |
| self.services, |
| self.auth, |
| self.me_user_id, |
| self.query, |
| self.query_project_names, |
| self.items_per_page, |
| self.paginate_start, |
| self.can, |
| self.group_by_spec, |
| self.sort_spec, |
| self.warnings, |
| self.errors, |
| self.use_cached_searches, |
| self.profiler, |
| project=self.project) |
| issues = self.MakeIssues(23) |
| filtered_iids = { |
| 0: [issue.issue_id for issue in issues], |
| } |
| |
| samples_by_shard, sample_iids_to_shard = pipeline._FetchAllSamples( |
| filtered_iids) |
| self.assertEqual(1, len(samples_by_shard)) |
| self.assertEqual(2, len(samples_by_shard[0])) |
| self.assertEqual(2, len(sample_iids_to_shard)) |
| for sample_iid in sample_iids_to_shard: |
| shard_key = sample_iids_to_shard[sample_iid] |
| self.assertIn(sample_iid, filtered_iids[shard_key]) |
| |
| def testChooseSampleIssues_Empty(self): |
| """When the search gave no results, there cannot be any samples.""" |
| pipeline = frontendsearchpipeline.FrontendSearchPipeline( |
| self.cnxn, |
| self.services, |
| self.auth, |
| self.me_user_id, |
| self.query, |
| self.query_project_names, |
| self.items_per_page, |
| self.paginate_start, |
| self.can, |
| self.group_by_spec, |
| self.sort_spec, |
| self.warnings, |
| self.errors, |
| self.use_cached_searches, |
| self.profiler, |
| project=self.project) |
| issue_ids = [] |
| on_hand_issues, needed_iids = pipeline._ChooseSampleIssues(issue_ids) |
| self.assertEqual({}, on_hand_issues) |
| self.assertEqual([], needed_iids) |
| |
| def testChooseSampleIssues_Small(self): |
| """When the search gave few results, don't bother with samples.""" |
| pipeline = frontendsearchpipeline.FrontendSearchPipeline( |
| self.cnxn, |
| self.services, |
| self.auth, |
| self.me_user_id, |
| self.query, |
| self.query_project_names, |
| self.items_per_page, |
| self.paginate_start, |
| self.can, |
| self.group_by_spec, |
| self.sort_spec, |
| self.warnings, |
| self.errors, |
| self.use_cached_searches, |
| self.profiler, |
| project=self.project) |
| issue_ids = [78901, 78902] |
| on_hand_issues, needed_iids = pipeline._ChooseSampleIssues(issue_ids) |
| self.assertEqual({}, on_hand_issues) |
| self.assertEqual([], needed_iids) |
| |
| def MakeIssues(self, num_issues): |
| issues = [] |
| for i in range(num_issues): |
| issue = fake.MakeTestIssue(789, 100 + i, 'samp test', 'New', 111) |
| issues.append(issue) |
| self.services.issue.TestAddIssue(issue) |
| return issues |
| |
| def testChooseSampleIssues_Normal(self): |
| """We will choose at least one sample for every 10 results in a shard.""" |
| pipeline = frontendsearchpipeline.FrontendSearchPipeline( |
| self.cnxn, |
| self.services, |
| self.auth, |
| self.me_user_id, |
| self.query, |
| self.query_project_names, |
| self.items_per_page, |
| self.paginate_start, |
| self.can, |
| self.group_by_spec, |
| self.sort_spec, |
| self.warnings, |
| self.errors, |
| self.use_cached_searches, |
| self.profiler, |
| project=self.project) |
| issues = self.MakeIssues(23) |
| issue_ids = [issue.issue_id for issue in issues] |
| on_hand_issues, needed_iids = pipeline._ChooseSampleIssues(issue_ids) |
| self.assertEqual({}, on_hand_issues) |
| self.assertEqual(2, len(needed_iids)) |
| for sample_iid in needed_iids: |
| self.assertIn(sample_iid, issue_ids) |
| |
| def testLookupNeededUsers(self): |
| pipeline = frontendsearchpipeline.FrontendSearchPipeline( |
| self.cnxn, |
| self.services, |
| self.auth, |
| self.me_user_id, |
| self.query, |
| self.query_project_names, |
| self.items_per_page, |
| self.paginate_start, |
| self.can, |
| self.group_by_spec, |
| self.sort_spec, |
| self.warnings, |
| self.errors, |
| self.use_cached_searches, |
| self.profiler, |
| project=self.project) |
| |
| pipeline._LookupNeededUsers([]) |
| self.assertEqual([], list(pipeline.users_by_id.keys())) |
| |
| pipeline._LookupNeededUsers([self.issue_1, self.issue_2, self.issue_3]) |
| self.assertEqual([0, 111], list(pipeline.users_by_id.keys())) |
| |
| def testPaginate_List(self): |
| pipeline = frontendsearchpipeline.FrontendSearchPipeline( |
| self.cnxn, |
| self.services, |
| self.auth, |
| self.me_user_id, |
| self.query, |
| self.query_project_names, |
| self.items_per_page, |
| self.paginate_start, |
| self.can, |
| self.group_by_spec, |
| self.sort_spec, |
| self.warnings, |
| self.errors, |
| self.use_cached_searches, |
| self.profiler, |
| project=self.project) |
| pipeline.allowed_iids = [ |
| self.issue_1.issue_id, self.issue_2.issue_id, self.issue_3.issue_id] |
| pipeline.allowed_results = [self.issue_1, self.issue_2, self.issue_3] |
| pipeline.total_count = len(pipeline.allowed_results) |
| pipeline.Paginate() |
| self.assertEqual( |
| [self.issue_1, self.issue_2, self.issue_3], |
| pipeline.visible_results) |
| self.assertFalse(pipeline.pagination.limit_reached) |
| |
| |
| class FrontendSearchPipelineMethodsTest(unittest.TestCase): |
| |
| def setUp(self): |
| self.mox = mox.Mox() |
| self.testbed = testbed.Testbed() |
| self.testbed.activate() |
| self.testbed.init_user_stub() |
| self.testbed.init_memcache_stub() |
| |
| self.project_id = 789 |
| self.default_config = tracker_bizobj.MakeDefaultProjectIssueConfig( |
| self.project_id) |
| self.services = service_manager.Services( |
| project=fake.ProjectService()) |
| self.project = self.services.project.TestAddProject( |
| 'proj', project_id=self.project_id) |
| |
| def tearDown(self): |
| self.testbed.deactivate() |
| self.mox.UnsetStubs() |
| self.mox.ResetAll() |
| |
| def testMakeBackendCallback(self): |
| called_with = [] |
| |
| def func(a, b): |
| called_with.append((a, b)) |
| |
| callback = frontendsearchpipeline._MakeBackendCallback(func, 10, 20) |
| callback() |
| self.assertEqual([(10, 20)], called_with) |
| |
| def testParseUserQuery_CheckQuery(self): |
| warnings = [] |
| msg = frontendsearchpipeline._CheckQuery( |
| 'cnxn', self.services, 'ok query', self.default_config, |
| [self.project_id], True, warnings=warnings) |
| self.assertIsNone(msg) |
| self.assertEqual([], warnings) |
| |
| warnings = [] |
| msg = frontendsearchpipeline._CheckQuery( |
| 'cnxn', self.services, 'modified:0-0-0', self.default_config, |
| [self.project_id], True, warnings=warnings) |
| self.assertEqual( |
| 'Could not parse date: 0-0-0', |
| msg) |
| |
| warnings = [] |
| msg = frontendsearchpipeline._CheckQuery( |
| 'cnxn', self.services, 'blocking:3.14', self.default_config, |
| [self.project_id], True, warnings=warnings) |
| self.assertEqual( |
| 'Could not parse issue reference: 3.14', |
| msg) |
| self.assertEqual([], warnings) |
| |
| def testStartBackendSearch(self): |
| # TODO(jrobbins): write this test. |
| pass |
| |
| def testFinishBackendSearch(self): |
| # TODO(jrobbins): write this test. |
| pass |
| |
| def testGetProjectTimestamps_NoneSet(self): |
| project_shard_timestamps = frontendsearchpipeline._GetProjectTimestamps( |
| [], []) |
| self.assertEqual({}, project_shard_timestamps) |
| |
| project_shard_timestamps = frontendsearchpipeline._GetProjectTimestamps( |
| [], [(0, (0, 'p:v')), (1, (1, 'p:v')), (2, (2, 'p:v'))]) |
| self.assertEqual({}, project_shard_timestamps) |
| |
| project_shard_timestamps = frontendsearchpipeline._GetProjectTimestamps( |
| [789], [(0, (0, 'p:v')), (1, (1, 'p:v')), (2, (2, 'p:v'))]) |
| self.assertEqual({}, project_shard_timestamps) |
| |
| def testGetProjectTimestamps_SpecificProjects(self): |
| memcache.set('789;0', NOW) |
| memcache.set('789;1', NOW - 1000) |
| memcache.set('789;2', NOW - 3000) |
| project_shard_timestamps = frontendsearchpipeline._GetProjectTimestamps( |
| [789], [(0, (0, 'p:v')), (1, (1, 'p:v')), (2, (2, 'p:v'))]) |
| self.assertEqual( |
| { (789, 0): NOW, |
| (789, 1): NOW - 1000, |
| (789, 2): NOW - 3000, |
| }, |
| project_shard_timestamps) |
| |
| memcache.set('790;0', NOW) |
| memcache.set('790;1', NOW - 10000) |
| memcache.set('790;2', NOW - 30000) |
| project_shard_timestamps = frontendsearchpipeline._GetProjectTimestamps( |
| [789, 790], [(0, (0, 'p:v')), (1, (1, 'p:v')), (2, (2, 'p:v'))]) |
| self.assertEqual( |
| { (789, 0): NOW, |
| (789, 1): NOW - 1000, |
| (789, 2): NOW - 3000, |
| (790, 0): NOW, |
| (790, 1): NOW - 10000, |
| (790, 2): NOW - 30000, |
| }, |
| project_shard_timestamps) |
| |
| def testGetProjectTimestamps_SiteWide(self): |
| memcache.set('all;0', NOW) |
| memcache.set('all;1', NOW - 10000) |
| memcache.set('all;2', NOW - 30000) |
| project_shard_timestamps = frontendsearchpipeline._GetProjectTimestamps( |
| [], [(0, (0, 'p:v')), (1, (1, 'p:v')), (2, (2, 'p:v'))]) |
| self.assertEqual( |
| { ('all', 0): NOW, |
| ('all', 1): NOW - 10000, |
| ('all', 2): NOW - 30000, |
| }, |
| project_shard_timestamps) |
| |
| def testGetNonviewableIIDs_SearchMissSoNoOp(self): |
| """If search cache missed, don't bother looking up nonviewable IIDs.""" |
| unfiltered_iids_dict = {} # No cached search results found. |
| rpc_tuples = [] # Nothing should accumulate here in this case. |
| nonviewable_iids = {} # Nothing should accumulate here in this case. |
| processed_invalidations_up_to = 12345 |
| frontendsearchpipeline._GetNonviewableIIDs( |
| [789], 111, list(unfiltered_iids_dict.keys()), rpc_tuples, |
| nonviewable_iids, {}, processed_invalidations_up_to, True) |
| self.assertEqual([], rpc_tuples) |
| self.assertEqual({}, nonviewable_iids) |
| |
| def testGetNonviewableIIDs_SearchHitThenNonviewableHit(self): |
| """If search cache hit, get nonviewable info from cache.""" |
| unfiltered_iids_dict = { |
| 1: [10001, 10021], |
| 2: ['the search result issue_ids do not matter'], |
| } |
| rpc_tuples = [] # Nothing should accumulate here in this case. |
| nonviewable_iids = {} # Our mock results should end up here. |
| processed_invalidations_up_to = 12345 |
| memcache.set('nonviewable:789;111;1', |
| ([10001, 10031], processed_invalidations_up_to - 10)) |
| memcache.set('nonviewable:789;111;2', |
| ([10002, 10042], processed_invalidations_up_to - 30)) |
| |
| project_shard_timestamps = { |
| (789, 1): 0, # not stale |
| (789, 2): 0, # not stale |
| } |
| frontendsearchpipeline._GetNonviewableIIDs( |
| [789], 111, list(unfiltered_iids_dict.keys()), rpc_tuples, |
| nonviewable_iids, project_shard_timestamps, |
| processed_invalidations_up_to, True) |
| self.assertEqual([], rpc_tuples) |
| self.assertEqual({1: {10001, 10031}, 2: {10002, 10042}}, nonviewable_iids) |
| |
| def testGetNonviewableIIDs_SearchHitNonviewableMissSoStartRPC(self): |
| """If search hit and n-v miss, create RPCs to get nonviewable info.""" |
| self.mox.StubOutWithMock( |
| frontendsearchpipeline, '_StartBackendNonviewableCall') |
| unfiltered_iids_dict = { |
| 2: ['the search result issue_ids do not matter'], |
| } |
| rpc_tuples = [] # One RPC object should accumulate here. |
| nonviewable_iids = {} # This will stay empty until RPCs complete. |
| processed_invalidations_up_to = 12345 |
| # Nothing is set in memcache for this case. |
| a_fake_rpc = testing_helpers.Blank(callback=None) |
| frontendsearchpipeline._StartBackendNonviewableCall( |
| 789, 111, 2, processed_invalidations_up_to).AndReturn(a_fake_rpc) |
| self.mox.ReplayAll() |
| |
| frontendsearchpipeline._GetNonviewableIIDs( |
| [789], 111, list(unfiltered_iids_dict.keys()), rpc_tuples, |
| nonviewable_iids, {}, processed_invalidations_up_to, True) |
| self.mox.VerifyAll() |
| _, sid_0, rpc_0 = rpc_tuples[0] |
| self.assertEqual(2, sid_0) |
| self.assertEqual({}, nonviewable_iids) |
| self.assertEqual(a_fake_rpc, rpc_0) |
| self.assertIsNotNone(a_fake_rpc.callback) |
| |
| def testAccumulateNonviewableIIDs_MemcacheHitForProject(self): |
| processed_invalidations_up_to = 12345 |
| cached_dict = { |
| '789;111;2': ([10002, 10042], processed_invalidations_up_to - 10), |
| '789;111;3': ([10003, 10093], processed_invalidations_up_to - 30), |
| } |
| rpc_tuples = [] # Nothing should accumulate here. |
| nonviewable_iids = {1: {10001}} # This will gain the shard 2 values. |
| project_shard_timestamps = { |
| (789, 1): 0, # not stale |
| (789, 2): 0, # not stale |
| } |
| frontendsearchpipeline._AccumulateNonviewableIIDs( |
| 789, 111, 2, cached_dict, nonviewable_iids, project_shard_timestamps, |
| rpc_tuples, processed_invalidations_up_to) |
| self.assertEqual([], rpc_tuples) |
| self.assertEqual({1: {10001}, 2: {10002, 10042}}, nonviewable_iids) |
| |
| def testAccumulateNonviewableIIDs_MemcacheStaleForProject(self): |
| self.mox.StubOutWithMock( |
| frontendsearchpipeline, '_StartBackendNonviewableCall') |
| processed_invalidations_up_to = 12345 |
| cached_dict = { |
| '789;111;2': ([10002, 10042], processed_invalidations_up_to - 10), |
| '789;111;3': ([10003, 10093], processed_invalidations_up_to - 30), |
| } |
| rpc_tuples = [] # Nothing should accumulate here. |
| nonviewable_iids = {1: {10001}} # Nothing added here until RPC completes |
| project_shard_timestamps = { |
| (789, 1): 0, # not stale |
| (789, 2): processed_invalidations_up_to, # stale! |
| } |
| a_fake_rpc = testing_helpers.Blank(callback=None) |
| frontendsearchpipeline._StartBackendNonviewableCall( |
| 789, 111, 2, processed_invalidations_up_to).AndReturn(a_fake_rpc) |
| self.mox.ReplayAll() |
| |
| frontendsearchpipeline._AccumulateNonviewableIIDs( |
| 789, 111, 2, cached_dict, nonviewable_iids, project_shard_timestamps, |
| rpc_tuples, processed_invalidations_up_to) |
| self.mox.VerifyAll() |
| _, sid_0, rpc_0 = rpc_tuples[0] |
| self.assertEqual(2, sid_0) |
| self.assertEqual(a_fake_rpc, rpc_0) |
| self.assertIsNotNone(a_fake_rpc.callback) |
| self.assertEqual({1: {10001}}, nonviewable_iids) |
| |
| def testAccumulateNonviewableIIDs_MemcacheHitForWholeSite(self): |
| processed_invalidations_up_to = 12345 |
| cached_dict = { |
| 'all;111;2': ([10002, 10042], processed_invalidations_up_to - 10), |
| 'all;111;3': ([10003, 10093], processed_invalidations_up_to - 30), |
| } |
| rpc_tuples = [] # Nothing should accumulate here. |
| nonviewable_iids = {1: {10001}} # This will gain the shard 2 values. |
| project_shard_timestamps = { |
| (None, 1): 0, # not stale |
| (None, 2): 0, # not stale |
| } |
| frontendsearchpipeline._AccumulateNonviewableIIDs( |
| None, 111, 2, cached_dict, nonviewable_iids, project_shard_timestamps, |
| rpc_tuples, processed_invalidations_up_to) |
| self.assertEqual([], rpc_tuples) |
| self.assertEqual({1: {10001}, 2: {10002, 10042}}, nonviewable_iids) |
| |
| def testAccumulateNonviewableIIDs_MemcacheMissSoStartRPC(self): |
| self.mox.StubOutWithMock( |
| frontendsearchpipeline, '_StartBackendNonviewableCall') |
| cached_dict = {} # Nothing here, so it is an at-risk cache miss. |
| rpc_tuples = [] # One RPC should accumulate here. |
| nonviewable_iids = {1: {10001}} # Nothing added here until RPC completes. |
| processed_invalidations_up_to = 12345 |
| a_fake_rpc = testing_helpers.Blank(callback=None) |
| frontendsearchpipeline._StartBackendNonviewableCall( |
| 789, 111, 2, processed_invalidations_up_to).AndReturn(a_fake_rpc) |
| self.mox.ReplayAll() |
| |
| frontendsearchpipeline._AccumulateNonviewableIIDs( |
| 789, 111, 2, cached_dict, nonviewable_iids, {}, rpc_tuples, |
| processed_invalidations_up_to) |
| self.mox.VerifyAll() |
| _, sid_0, rpc_0 = rpc_tuples[0] |
| self.assertEqual(2, sid_0) |
| self.assertEqual(a_fake_rpc, rpc_0) |
| self.assertIsNotNone(a_fake_rpc.callback) |
| self.assertEqual({1: {10001}}, nonviewable_iids) |
| |
| def testGetCachedSearchResults(self): |
| # TODO(jrobbins): Write this test. |
| pass |
| |
| def testMakeBackendRequestHeaders(self): |
| headers = frontendsearchpipeline._MakeBackendRequestHeaders(False) |
| self.assertNotIn('X-AppEngine-FailFast', headers) |
| headers = frontendsearchpipeline._MakeBackendRequestHeaders(True) |
| self.assertEqual('Yes', headers['X-AppEngine-FailFast']) |
| |
| def testStartBackendSearchCall(self): |
| self.mox.StubOutWithMock(urlfetch, 'create_rpc') |
| self.mox.StubOutWithMock(urlfetch, 'make_fetch_call') |
| self.mox.StubOutWithMock(modules, 'get_hostname') |
| a_fake_rpc = testing_helpers.Blank(callback=None) |
| urlfetch.create_rpc(deadline=settings.backend_deadline).AndReturn( |
| a_fake_rpc) |
| modules.get_hostname(module='default') |
| urlfetch.make_fetch_call( |
| a_fake_rpc, mox.StrContains( |
| urls.BACKEND_SEARCH + '?groupby=cc&invalidation_timestep=12345&' |
| +'logged_in_user_id=777&me_user_ids=555&' |
| +'num=201&projects=proj&q=priority%3Dhigh&shard_id=2&start=0'), |
| follow_redirects=False, |
| headers=mox.IsA(dict)) |
| self.mox.ReplayAll() |
| |
| processed_invalidations_up_to = 12345 |
| me_user_ids = [555] |
| logged_in_user_id = 777 |
| new_url_num = 201 |
| frontendsearchpipeline._StartBackendSearchCall( |
| ['proj'], (2, 'priority=high'), |
| processed_invalidations_up_to, |
| me_user_ids, |
| logged_in_user_id, |
| new_url_num, |
| group_by_spec='cc') |
| self.mox.VerifyAll() |
| |
| def testStartBackendSearchCall_SortAndGroup(self): |
| self.mox.StubOutWithMock(urlfetch, 'create_rpc') |
| self.mox.StubOutWithMock(urlfetch, 'make_fetch_call') |
| self.mox.StubOutWithMock(modules, 'get_hostname') |
| a_fake_rpc = testing_helpers.Blank(callback=None) |
| urlfetch.create_rpc(deadline=settings.backend_deadline).AndReturn( |
| a_fake_rpc) |
| modules.get_hostname(module='default') |
| urlfetch.make_fetch_call( |
| a_fake_rpc, |
| mox.StrContains( |
| urls.BACKEND_SEARCH + '?groupby=bar&' + |
| 'invalidation_timestep=12345&' + |
| 'logged_in_user_id=777&me_user_ids=555&num=201&projects=proj&' + |
| 'q=priority%3Dhigh&shard_id=2&sort=foo&start=0'), |
| follow_redirects=False, |
| headers=mox.IsA(dict)) |
| self.mox.ReplayAll() |
| |
| processed_invalidations_up_to = 12345 |
| me_user_ids = [555] |
| logged_in_user_id = 777 |
| new_url_num = 201 |
| sort_spec = 'foo' |
| group_by_spec = 'bar' |
| frontendsearchpipeline._StartBackendSearchCall( |
| ['proj'], (2, 'priority=high'), |
| processed_invalidations_up_to, |
| me_user_ids, |
| logged_in_user_id, |
| new_url_num, |
| sort_spec=sort_spec, |
| group_by_spec=group_by_spec) |
| self.mox.VerifyAll() |
| |
| def testStartBackendNonviewableCall(self): |
| self.mox.StubOutWithMock(urlfetch, 'create_rpc') |
| self.mox.StubOutWithMock(urlfetch, 'make_fetch_call') |
| self.mox.StubOutWithMock(modules, 'get_hostname') |
| a_fake_rpc = testing_helpers.Blank(callback=None) |
| urlfetch.create_rpc(deadline=settings.backend_deadline).AndReturn( |
| a_fake_rpc) |
| modules.get_hostname(module='default') |
| urlfetch.make_fetch_call( |
| a_fake_rpc, mox.StrContains(urls.BACKEND_NONVIEWABLE), |
| follow_redirects=False, headers=mox.IsA(dict)) |
| self.mox.ReplayAll() |
| |
| processed_invalidations_up_to = 12345 |
| frontendsearchpipeline._StartBackendNonviewableCall( |
| 789, 111, 2, processed_invalidations_up_to) |
| self.mox.VerifyAll() |
| |
| def testHandleBackendSearchResponse_500(self): |
| response_str = 'There was a problem processing the query.' |
| rpc = testing_helpers.Blank( |
| get_result=lambda: testing_helpers.Blank( |
| content=response_str, status_code=500)) |
| rpc_tuple = (NOW, 2, rpc) |
| rpc_tuples = [] # Nothing should be added for this case. |
| filtered_iids = {} # Search results should accumlate here, per-shard. |
| search_limit_reached = {} # Booleans accumulate here, per-shard. |
| processed_invalidations_up_to = 12345 |
| |
| me_user_ids = [111] |
| logged_in_user_id = 0 |
| new_url_num = 100 |
| error_responses = set() |
| |
| self.mox.StubOutWithMock(frontendsearchpipeline, '_StartBackendSearchCall') |
| frontendsearchpipeline._HandleBackendSearchResponse( |
| ['proj'], rpc_tuple, rpc_tuples, 0, filtered_iids, search_limit_reached, |
| processed_invalidations_up_to, error_responses, me_user_ids, |
| logged_in_user_id, new_url_num, 1, None, None) |
| self.assertEqual([], rpc_tuples) |
| self.assertIn(2, error_responses) |
| |
| def testHandleBackendSearchResponse_Error(self): |
| response_str = ( |
| '})]\'\n' |
| '{' |
| ' "unfiltered_iids": [],' |
| ' "search_limit_reached": false,' |
| ' "error": "Invalid query"' |
| '}' |
| ) |
| rpc = testing_helpers.Blank( |
| get_result=lambda: testing_helpers.Blank( |
| content=response_str, status_code=200)) |
| rpc_tuple = (NOW, 2, rpc) |
| rpc_tuples = [] # Nothing should be added for this case. |
| filtered_iids = {} # Search results should accumlate here, per-shard. |
| search_limit_reached = {} # Booleans accumulate here, per-shard. |
| processed_invalidations_up_to = 12345 |
| |
| me_user_ids = [111] |
| logged_in_user_id = 0 |
| new_url_num = 100 |
| error_responses = set() |
| frontendsearchpipeline._HandleBackendSearchResponse( |
| ['proj'], rpc_tuple, rpc_tuples, 2, filtered_iids, search_limit_reached, |
| processed_invalidations_up_to, error_responses, me_user_ids, |
| logged_in_user_id, new_url_num, 1, None, None) |
| self.assertEqual([], rpc_tuples) |
| self.assertEqual({2: []}, filtered_iids) |
| self.assertEqual({2: False}, search_limit_reached) |
| self.assertEqual({2}, error_responses) |
| |
| def testHandleBackendSearchResponse_Normal(self): |
| response_str = ( |
| '})]\'\n' |
| '{' |
| ' "unfiltered_iids": [10002, 10042],' |
| ' "search_limit_reached": false' |
| '}' |
| ) |
| rpc = testing_helpers.Blank( |
| get_result=lambda: testing_helpers.Blank( |
| content=response_str, status_code=200)) |
| rpc_tuple = (NOW, 2, rpc) |
| rpc_tuples = [] # Nothing should be added for this case. |
| filtered_iids = {} # Search results should accumlate here, per-shard. |
| search_limit_reached = {} # Booleans accumulate here, per-shard. |
| processed_invalidations_up_to = 12345 |
| |
| me_user_ids = [111] |
| logged_in_user_id = 0 |
| new_url_num = 100 |
| error_responses = set() |
| frontendsearchpipeline._HandleBackendSearchResponse( |
| ['proj'], rpc_tuple, rpc_tuples, 2, filtered_iids, search_limit_reached, |
| processed_invalidations_up_to, error_responses, me_user_ids, |
| logged_in_user_id, new_url_num, 1, None, None) |
| self.assertEqual([], rpc_tuples) |
| self.assertEqual({2: [10002, 10042]}, filtered_iids) |
| self.assertEqual({2: False}, search_limit_reached) |
| |
| def testHandleBackendSearchResponse_TriggersRetry(self): |
| response_str = None |
| rpc = testing_helpers.Blank( |
| get_result=lambda: testing_helpers.Blank(content=response_str)) |
| rpc_tuple = (NOW, 2, rpc) |
| rpc_tuples = [] # New RPC should be appended here |
| filtered_iids = {} # No change here until retry completes. |
| search_limit_reached = {} # No change here until retry completes. |
| processed_invalidations_up_to = 12345 |
| error_responses = set() |
| |
| me_user_ids = [111] |
| logged_in_user_id = 0 |
| new_url_num = 100 |
| |
| self.mox.StubOutWithMock(frontendsearchpipeline, '_StartBackendSearchCall') |
| a_fake_rpc = testing_helpers.Blank(callback=None) |
| rpc = frontendsearchpipeline._StartBackendSearchCall( |
| ['proj'], |
| 2, |
| processed_invalidations_up_to, |
| me_user_ids, |
| logged_in_user_id, |
| new_url_num, |
| can=1, |
| group_by_spec=None, |
| sort_spec=None, |
| failfast=False).AndReturn(a_fake_rpc) |
| self.mox.ReplayAll() |
| |
| frontendsearchpipeline._HandleBackendSearchResponse( |
| ['proj'], rpc_tuple, rpc_tuples, 2, filtered_iids, search_limit_reached, |
| processed_invalidations_up_to, error_responses, me_user_ids, |
| logged_in_user_id, new_url_num, 1, None, None) |
| self.mox.VerifyAll() |
| _, retry_shard_id, retry_rpc = rpc_tuples[0] |
| self.assertEqual(2, retry_shard_id) |
| self.assertEqual(a_fake_rpc, retry_rpc) |
| self.assertIsNotNone(retry_rpc.callback) |
| self.assertEqual({}, filtered_iids) |
| self.assertEqual({}, search_limit_reached) |
| |
| def testHandleBackendNonviewableResponse_Error(self): |
| response_str = 'There was an error.' |
| rpc = testing_helpers.Blank( |
| get_result=lambda: testing_helpers.Blank( |
| content=response_str, |
| status_code=500 |
| )) |
| rpc_tuple = (NOW, 2, rpc) |
| rpc_tuples = [] # Nothing should be added for this case. |
| nonviewable_iids = {} # At-risk issue IDs should accumlate here, per-shard. |
| processed_invalidations_up_to = 12345 |
| |
| self.mox.StubOutWithMock( |
| frontendsearchpipeline, '_StartBackendNonviewableCall') |
| frontendsearchpipeline._HandleBackendNonviewableResponse( |
| 789, 111, 2, rpc_tuple, rpc_tuples, 0, nonviewable_iids, |
| processed_invalidations_up_to) |
| self.assertEqual([], rpc_tuples) |
| self.assertNotEqual({2: {10002, 10042}}, nonviewable_iids) |
| |
| def testHandleBackendNonviewableResponse_Normal(self): |
| response_str = ( |
| '})]\'\n' |
| '{' |
| ' "nonviewable": [10002, 10042]' |
| '}' |
| ) |
| rpc = testing_helpers.Blank( |
| get_result=lambda: testing_helpers.Blank( |
| content=response_str, |
| status_code=200 |
| )) |
| rpc_tuple = (NOW, 2, rpc) |
| rpc_tuples = [] # Nothing should be added for this case. |
| nonviewable_iids = {} # At-risk issue IDs should accumlate here, per-shard. |
| processed_invalidations_up_to = 12345 |
| |
| frontendsearchpipeline._HandleBackendNonviewableResponse( |
| 789, 111, 2, rpc_tuple, rpc_tuples, 2, nonviewable_iids, |
| processed_invalidations_up_to) |
| self.assertEqual([], rpc_tuples) |
| self.assertEqual({2: {10002, 10042}}, nonviewable_iids) |
| |
| def testHandleBackendAtRiskResponse_TriggersRetry(self): |
| response_str = None |
| rpc = testing_helpers.Blank( |
| get_result=lambda: testing_helpers.Blank(content=response_str)) |
| rpc_tuple = (NOW, 2, rpc) |
| rpc_tuples = [] # New RPC should be appended here |
| nonviewable_iids = {} # No change here until retry completes. |
| processed_invalidations_up_to = 12345 |
| |
| self.mox.StubOutWithMock( |
| frontendsearchpipeline, '_StartBackendNonviewableCall') |
| a_fake_rpc = testing_helpers.Blank(callback=None) |
| rpc = frontendsearchpipeline._StartBackendNonviewableCall( |
| 789, 111, 2, processed_invalidations_up_to, failfast=False |
| ).AndReturn(a_fake_rpc) |
| self.mox.ReplayAll() |
| |
| frontendsearchpipeline._HandleBackendNonviewableResponse( |
| 789, 111, 2, rpc_tuple, rpc_tuples, 2, nonviewable_iids, |
| processed_invalidations_up_to) |
| self.mox.VerifyAll() |
| _, retry_shard_id, retry_rpc = rpc_tuples[0] |
| self.assertEqual(2, retry_shard_id) |
| self.assertIsNotNone(retry_rpc.callback) |
| self.assertEqual(a_fake_rpc, retry_rpc) |
| self.assertEqual({}, nonviewable_iids) |
| |
| def testSortIssues(self): |
| services = service_manager.Services( |
| cache_manager=fake.CacheManager()) |
| sorting.InitializeArtValues(services) |
| |
| issue_1 = fake.MakeTestIssue( |
| 789, 1, 'one', 'New', 111, labels=['Priority-High']) |
| issue_2 = fake.MakeTestIssue( |
| 789, 2, 'two', 'New', 111, labels=['Priority-Low']) |
| issue_3 = fake.MakeTestIssue( |
| 789, 3, 'three', 'New', 111, labels=['Priority-Medium']) |
| issues = [issue_1, issue_2, issue_3] |
| config = tracker_bizobj.MakeDefaultProjectIssueConfig(789) |
| |
| sorted_issues = frontendsearchpipeline._SortIssues( |
| issues, config, {}, '', 'priority') |
| |
| self.assertEqual( |
| [issue_1, issue_3, issue_2], # Order is high, medium, low. |
| sorted_issues) |
| |
| |
| class FrontendSearchPipelineShardMethodsTest(unittest.TestCase): |
| |
| def setUp(self): |
| self.sharded_iids = { |
| (0, 'p:v'): [10, 20, 30, 40, 50], |
| (1, 'p:v'): [21, 41, 61, 81], |
| (2, 'p:v'): [42, 52, 62, 72, 102], |
| (3, 'p:v'): [], |
| } |
| |
| def testTotalLength_Empty(self): |
| """If there were no results, the length of the sharded list is zero.""" |
| self.assertEqual(0, frontendsearchpipeline._TotalLength({})) |
| |
| def testTotalLength_Normal(self): |
| """The length of the sharded list is the sum of the shard lengths.""" |
| self.assertEqual( |
| 14, frontendsearchpipeline._TotalLength(self.sharded_iids)) |
| |
| def testReverseShards_Empty(self): |
| """Reversing an empty sharded list is still empty.""" |
| empty_sharded_iids = {} |
| frontendsearchpipeline._ReverseShards(empty_sharded_iids) |
| self.assertEqual({}, empty_sharded_iids) |
| |
| def testReverseShards_Normal(self): |
| """Reversing a sharded list reverses each shard.""" |
| frontendsearchpipeline._ReverseShards(self.sharded_iids) |
| self.assertEqual( |
| {(0, 'p:v'): [50, 40, 30, 20, 10], |
| (1, 'p:v'): [81, 61, 41, 21], |
| (2, 'p:v'): [102, 72, 62, 52, 42], |
| (3, 'p:v'): [], |
| }, |
| self.sharded_iids) |
| |
| def testTrimShardedIIDs_Empty(self): |
| """If the sharded list is empty, trimming it makes no change.""" |
| empty_sharded_iids = {} |
| frontendsearchpipeline._TrimEndShardedIIDs(empty_sharded_iids, [], 12) |
| self.assertEqual({}, empty_sharded_iids) |
| |
| frontendsearchpipeline._TrimEndShardedIIDs( |
| empty_sharded_iids, |
| [(100, (0, 'p:v')), (88, (8, 'p:v')), (99, (9, 'p:v'))], |
| 12) |
| self.assertEqual({}, empty_sharded_iids) |
| |
| def testTrimShardedIIDs_NoSamples(self): |
| """If there are no samples, we don't trim off any IIDs.""" |
| orig_sharded_iids = { |
| shard_id: iids[:] for shard_id, iids in self.sharded_iids.items()} |
| num_trimmed = frontendsearchpipeline._TrimEndShardedIIDs( |
| self.sharded_iids, [], 12) |
| self.assertEqual(0, num_trimmed) |
| self.assertEqual(orig_sharded_iids, self.sharded_iids) |
| |
| num_trimmed = frontendsearchpipeline._TrimEndShardedIIDs( |
| self.sharded_iids, [], 1) |
| self.assertEqual(0, num_trimmed) |
| self.assertEqual(orig_sharded_iids, self.sharded_iids) |
| |
| def testTrimShardedIIDs_Normal(self): |
| """The first 3 samples contribute all needed IIDs, so trim off the rest.""" |
| samples = [(30, (0, 'p:v')), (41, (1, 'p:v')), (62, (2, 'p:v')), |
| (40, (0, 'p:v')), (81, (1, 'p:v'))] |
| num_trimmed = frontendsearchpipeline._TrimEndShardedIIDs( |
| self.sharded_iids, samples, 5) |
| self.assertEqual(2 + 1 + 0 + 0, num_trimmed) |
| self.assertEqual( |
| { # shard_id: iids before lower-bound + iids before 1st excess sample. |
| (0, 'p:v'): [10, 20] + [30], |
| (1, 'p:v'): [21] + [41, 61], |
| (2, 'p:v'): [42, 52] + [62, 72, 102], |
| (3, 'p:v'): [] + []}, |
| self.sharded_iids) |
| |
| def testCalcSamplePositions_Empty(self): |
| sharded_iids = {0: []} |
| samples = [] |
| self.assertEqual( |
| [], frontendsearchpipeline._CalcSamplePositions(sharded_iids, samples)) |
| |
| sharded_iids = {0: [10, 20, 30, 40]} |
| samples = [] |
| self.assertEqual( |
| [], frontendsearchpipeline._CalcSamplePositions(sharded_iids, samples)) |
| |
| sharded_iids = {0: []} |
| # E.g., the IIDs 2 and 4 might have been trimmed out in the forward phase. |
| # But we still have them in the list for the backwards phase, and they |
| # should just not contribute anything to the result. |
| samples = [(2, (2, 'p:v')), (4, (4, 'p:v'))] |
| self.assertEqual( |
| [], frontendsearchpipeline._CalcSamplePositions(sharded_iids, samples)) |
| |
| def testCalcSamplePositions_Normal(self): |
| samples = [(30, (0, 'p:v')), (41, (1, 'p:v')), (62, (2, 'p:v')), |
| (40, (0, 'p:v')), (81, (1, 'p:v'))] |
| self.assertEqual( |
| [(30, (0, 'p:v'), 2), |
| (41, (1, 'p:v'), 1), |
| (62, (2, 'p:v'), 2), |
| (40, (0, 'p:v'), 3), |
| (81, (1, 'p:v'), 3)], |
| frontendsearchpipeline._CalcSamplePositions(self.sharded_iids, samples)) |