blob: 4b1feb3363b2bb66aa6b8cab39d2a10c80500659 [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001# Copyright 2016 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style
3# license that can be found in the LICENSE file or at
4# https://developers.google.com/open-source/licenses/bsd
5
6"""Unit tests for sorting.py functions."""
7from __future__ import print_function
8from __future__ import division
9from __future__ import absolute_import
10
11import unittest
12# For convenient debugging
13import logging
14
15import mox
16
17from framework import sorting
18from framework import framework_views
19from proto import tracker_pb2
20from testing import fake
21from testing import testing_helpers
22from tracker import tracker_bizobj
23
24
25def MakeDescending(accessor):
26 return sorting._MaybeMakeDescending(accessor, True)
27
28
29class DescendingValueTest(unittest.TestCase):
30
31 def testMinString(self):
32 """When sorting desc, a min string will sort last instead of first."""
33 actual = sorting.DescendingValue.MakeDescendingValue(sorting.MIN_STRING)
34 self.assertEqual(sorting.MAX_STRING, actual)
35
36 def testMaxString(self):
37 """When sorting desc, a max string will sort first instead of last."""
38 actual = sorting.DescendingValue.MakeDescendingValue(sorting.MAX_STRING)
39 self.assertEqual(sorting.MIN_STRING, actual)
40
41 def testDescValues(self):
42 """The point of DescendingValue is to reverse the sort order."""
43 anti_a = sorting.DescendingValue.MakeDescendingValue('a')
44 anti_b = sorting.DescendingValue.MakeDescendingValue('b')
45 self.assertTrue(anti_a > anti_b)
46
47 def testMaybeMakeDescending(self):
48 """It returns an accessor that makes DescendingValue iff arg is True."""
49 asc_accessor = sorting._MaybeMakeDescending(lambda issue: 'a', False)
50 asc_value = asc_accessor('fake issue')
51 self.assertTrue(asc_value is 'a')
52
53 desc_accessor = sorting._MaybeMakeDescending(lambda issue: 'a', True)
54 print(desc_accessor)
55 desc_value = desc_accessor('fake issue')
56 self.assertTrue(isinstance(desc_value, sorting.DescendingValue))
57
58
59class SortingTest(unittest.TestCase):
60
61 def setUp(self):
62 self.mox = mox.Mox()
63 self.default_cols = 'a b c'
64 self.builtin_cols = 'a b x y z'
65 self.config = tracker_bizobj.MakeDefaultProjectIssueConfig(789)
66 self.config.component_defs.append(tracker_bizobj.MakeComponentDef(
67 11, 789, 'Database', 'doc', False, [], [], 0, 0))
68 self.config.component_defs.append(tracker_bizobj.MakeComponentDef(
69 22, 789, 'User Interface', 'doc', True, [], [], 0, 0))
70 self.config.component_defs.append(tracker_bizobj.MakeComponentDef(
71 33, 789, 'Installer', 'doc', False, [], [], 0, 0))
72
73 def tearDown(self):
74 self.mox.UnsetStubs()
75 self.mox.ResetAll()
76
77 def testMakeSingleSortKeyAccessor_Status(self):
78 """Sorting by status should create an accessor for that column."""
79 self.mox.StubOutWithMock(sorting, '_IndexOrLexical')
80 status_names = [wks.status for wks in self.config.well_known_statuses]
81 sorting._IndexOrLexical(status_names, 'status accessor')
82 self.mox.ReplayAll()
83
84 sorting._MakeSingleSortKeyAccessor(
85 'status', self.config, {'status': 'status accessor'}, [], {}, [])
86 self.mox.VerifyAll()
87
88 def testMakeSingleSortKeyAccessor_Component(self):
89 """Sorting by component should create an accessor for that column."""
90 self.mox.StubOutWithMock(sorting, '_IndexListAccessor')
91 component_ids = [11, 33, 22]
92 sorting._IndexListAccessor(component_ids, 'component accessor')
93 self.mox.ReplayAll()
94
95 sorting._MakeSingleSortKeyAccessor(
96 'component', self.config, {'component': 'component accessor'}, [], {}, [])
97 self.mox.VerifyAll()
98
99 def testMakeSingleSortKeyAccessor_OtherBuiltInColunms(self):
100 """Sorting a built-in column should create an accessor for that column."""
101 accessor = sorting._MakeSingleSortKeyAccessor(
102 'buildincol', self.config, {'buildincol': 'accessor'}, [], {}, [])
103 self.assertEqual('accessor', accessor)
104
105 def testMakeSingleSortKeyAccessor_WithPostProcessor(self):
106 """Sorting a built-in user column should create a user accessor."""
107 self.mox.StubOutWithMock(sorting, '_MakeAccessorWithPostProcessor')
108 users_by_id = {111: 'fake user'}
109 sorting._MakeAccessorWithPostProcessor(
110 users_by_id, 'mock owner accessor', 'mock postprocessor')
111 self.mox.ReplayAll()
112
113 sorting._MakeSingleSortKeyAccessor(
114 'owner', self.config, {'owner': 'mock owner accessor'},
115 {'owner': 'mock postprocessor'}, users_by_id, [])
116 self.mox.VerifyAll()
117
118 def testIndexOrLexical(self):
119 well_known_values = ['x-a', 'x-b', 'x-c', 'x-d']
120 art = 'this is a fake artifact'
121
122 # Case 1: accessor generates no values.
123 base_accessor = lambda art: None
124 accessor = sorting._IndexOrLexical(well_known_values, base_accessor)
125 self.assertEqual(sorting.MAX_STRING, accessor(art))
126 neg_accessor = MakeDescending(accessor)
127 self.assertEqual(sorting.DescendingValue(sorting.MAX_STRING),
128 neg_accessor(art))
129
130 # Case 2: accessor generates a value, but it is an empty value.
131 base_accessor = lambda art: ''
132 accessor = sorting._IndexOrLexical(well_known_values, base_accessor)
133 self.assertEqual(sorting.MAX_STRING, accessor(art))
134 neg_accessor = MakeDescending(accessor)
135 self.assertEqual(sorting.DescendingValue(sorting.MAX_STRING),
136 neg_accessor(art))
137
138 # Case 3: A single well-known value
139 base_accessor = lambda art: 'x-c'
140 accessor = sorting._IndexOrLexical(well_known_values, base_accessor)
141 self.assertEqual(2, accessor(art))
142 neg_accessor = MakeDescending(accessor)
143 self.assertEqual(-2, neg_accessor(art))
144
145 # Case 4: A single odd-ball value
146 base_accessor = lambda art: 'x-zzz'
147 accessor = sorting._IndexOrLexical(well_known_values, base_accessor)
148 self.assertEqual('x-zzz', accessor(art))
149 neg_accessor = MakeDescending(accessor)
150 self.assertEqual(
151 sorting.DescendingValue('x-zzz'), neg_accessor(art))
152
153 def testIndexListAccessor_SomeWellKnownValues(self):
154 """Values sort according to their position in the well-known list."""
155 well_known_values = [11, 33, 22] # These represent component IDs.
156 art = fake.MakeTestIssue(789, 1, 'sum 1', 'New', 111)
157 base_accessor = lambda issue: issue.component_ids
158 accessor = sorting._IndexListAccessor(well_known_values, base_accessor)
159
160 # Case 1: accessor generates no values.
161 self.assertEqual(sorting.MAX_STRING, accessor(art))
162 neg_accessor = MakeDescending(accessor)
163 self.assertEqual(sorting.MAX_STRING, neg_accessor(art))
164
165 # Case 2: A single well-known value
166 art.component_ids = [33]
167 self.assertEqual([1], accessor(art))
168 neg_accessor = MakeDescending(accessor)
169 self.assertEqual([-1], neg_accessor(art))
170
171 # Case 3: Multiple well-known and odd-ball values
172 art.component_ids = [33, 11, 99]
173 self.assertEqual([0, 1, sorting.MAX_STRING], accessor(art))
174 neg_accessor = MakeDescending(accessor)
175 self.assertEqual([sorting.MAX_STRING, -1, 0],
176 neg_accessor(art))
177
178 def testIndexListAccessor_NoWellKnownValues(self):
179 """When there are no well-known values, all values sort last."""
180 well_known_values = [] # Nothing pre-defined, so everything is oddball
181 art = fake.MakeTestIssue(789, 1, 'sum 1', 'New', 111)
182 base_accessor = lambda issue: issue.component_ids
183 accessor = sorting._IndexListAccessor(well_known_values, base_accessor)
184
185 # Case 1: accessor generates no values.
186 self.assertEqual(sorting.MAX_STRING, accessor(art))
187 neg_accessor = MakeDescending(accessor)
188 self.assertEqual(sorting.MAX_STRING, neg_accessor(art))
189
190 # Case 2: A single oddball value
191 art.component_ids = [33]
192 self.assertEqual([sorting.MAX_STRING], accessor(art))
193 neg_accessor = MakeDescending(accessor)
194 self.assertEqual([sorting.MAX_STRING], neg_accessor(art))
195
196 # Case 3: Multiple odd-ball values
197 art.component_ids = [33, 11, 99]
198 self.assertEqual(
199 [sorting.MAX_STRING, sorting.MAX_STRING, sorting.MAX_STRING],
200 accessor(art))
201 neg_accessor = MakeDescending(accessor)
202 self.assertEqual(
203 [sorting.MAX_STRING, sorting.MAX_STRING, sorting.MAX_STRING],
204 neg_accessor(art))
205
206 def testIndexOrLexicalList(self):
207 well_known_values = ['Pri-High', 'Pri-Med', 'Pri-Low']
208 art = fake.MakeTestIssue(789, 1, 'sum 1', 'New', 111, merged_into=200001)
209
210 # Case 1: accessor generates no values.
211 accessor = sorting._IndexOrLexicalList(well_known_values, [], 'pri', {})
212 self.assertEqual(sorting.MAX_STRING, accessor(art))
213 neg_accessor = MakeDescending(accessor)
214 self.assertEqual(sorting.MAX_STRING, neg_accessor(art))
215
216 # Case 2: A single well-known value
217 art.labels = ['Pri-Med']
218 accessor = sorting._IndexOrLexicalList(well_known_values, [], 'pri', {})
219 self.assertEqual([1], accessor(art))
220 neg_accessor = MakeDescending(accessor)
221 self.assertEqual([-1], neg_accessor(art))
222
223 # Case 3: Multiple well-known and odd-ball values
224 art.labels = ['Pri-zzz', 'Pri-Med', 'yyy', 'Pri-High']
225 accessor = sorting._IndexOrLexicalList(well_known_values, [], 'pri', {})
226 self.assertEqual([0, 1, 'zzz'], accessor(art))
227 neg_accessor = MakeDescending(accessor)
228 self.assertEqual([sorting.DescendingValue('zzz'), -1, 0],
229 neg_accessor(art))
230
231 # Case 4: Multi-part prefix.
232 well_known_values.extend(['X-Y-Header', 'X-Y-Footer'])
233 art.labels = ['X-Y-Footer', 'X-Y-Zone', 'X-Y-Header', 'X-Y-Area']
234 accessor = sorting._IndexOrLexicalList(well_known_values, [], 'x-y', {})
235 self.assertEqual([3, 4, 'area', 'zone'], accessor(art))
236 neg_accessor = MakeDescending(accessor)
237 self.assertEqual([sorting.DescendingValue('zone'),
238 sorting.DescendingValue('area'), -4, -3],
239 neg_accessor(art))
240
241 def testIndexOrLexicalList_CustomFields(self):
242 art = fake.MakeTestIssue(789, 1, 'sum 2', 'New', 111)
243 art.labels = ['samename-value1']
244 art.field_values = [tracker_bizobj.MakeFieldValue(
245 3, 6078, None, None, None, None, False)]
246
247 all_field_defs = [
248 tracker_bizobj.MakeFieldDef(
249 3, 789, 'samename', tracker_pb2.FieldTypes.INT_TYPE,
250 None, None, False, False, False, None, None, None, False, None,
251 None, None, None, 'cow spots', False),
252 tracker_bizobj.MakeFieldDef(
253 4, 788, 'samename', tracker_pb2.FieldTypes.APPROVAL_TYPE,
254 None, None, False, False, False, None, None, None, False, None,
255 None, None, None, 'cow spots', False),
256 tracker_bizobj.MakeFieldDef(
257 4, 788, 'notsamename', tracker_pb2.FieldTypes.APPROVAL_TYPE,
258 None, None, False, False, False, None, None, None, False, None,
259 None, None, None, 'should get filtered out', False)
260 ]
261
262 accessor = sorting._IndexOrLexicalList([], all_field_defs, 'samename', {})
263 self.assertEqual([6078, 'value1'], accessor(art))
264 neg_accessor = MakeDescending(accessor)
265 self.assertEqual(
266 [sorting.DescendingValue('value1'), -6078], neg_accessor(art))
267
268 def testIndexOrLexicalList_PhaseCustomFields(self):
269 art = fake.MakeTestIssue(789, 1, 'sum 2', 'New', 111)
270 art.labels = ['summer.goats-value1']
271 art.field_values = [
272 tracker_bizobj.MakeFieldValue(
273 3, 33, None, None, None, None, False, phase_id=77),
274 tracker_bizobj.MakeFieldValue(
275 3, 34, None, None, None, None, False, phase_id=77),
276 tracker_bizobj.MakeFieldValue(
277 3, 1000, None, None, None, None, False, phase_id=78)]
278 art.phases = [tracker_pb2.Phase(phase_id=77, name='summer'),
279 tracker_pb2.Phase(phase_id=78, name='winter')]
280
281 all_field_defs = [
282 tracker_bizobj.MakeFieldDef(
283 3, 789, 'goats', tracker_pb2.FieldTypes.INT_TYPE,
284 None, None, False, False, True, None, None, None, False, None,
285 None, None, None, 'goats love mineral', False, is_phase_field=True),
286 tracker_bizobj.MakeFieldDef(
287 4, 788, 'boo', tracker_pb2.FieldTypes.APPROVAL_TYPE,
288 None, None, False, False, False, None, None, None, False, None,
289 None, None, None, 'ahh', False),
290 ]
291
292 accessor = sorting._IndexOrLexicalList(
293 [], all_field_defs, 'summer.goats', {})
294 self.assertEqual([33, 34, 'value1'], accessor(art))
295 neg_accessor = MakeDescending(accessor)
296 self.assertEqual(
297 [sorting.DescendingValue('value1'), -34, -33], neg_accessor(art))
298
299 def testIndexOrLexicalList_ApprovalStatus(self):
300 art = fake.MakeTestIssue(789, 1, 'sum 2', 'New', 111)
301 art.labels = ['samename-value1']
302 art.approval_values = [tracker_pb2.ApprovalValue(approval_id=4)]
303
304 all_field_defs = [
305 tracker_bizobj.MakeFieldDef(
306 3, 789, 'samename', tracker_pb2.FieldTypes.INT_TYPE,
307 None, None, False, False, False, None, None, None, False, None,
308 None, None, None, 'cow spots', False),
309 tracker_bizobj.MakeFieldDef(
310 4, 788, 'samename', tracker_pb2.FieldTypes.APPROVAL_TYPE,
311 None, None, False, False, False, None, None, None, False, None,
312 None, None, None, 'cow spots', False)
313 ]
314
315 accessor = sorting._IndexOrLexicalList([], all_field_defs, 'samename', {})
316 self.assertEqual([0, 'value1'], accessor(art))
317 neg_accessor = MakeDescending(accessor)
318 self.assertEqual([sorting.DescendingValue('value1'),
319 sorting.DescendingValue(0)],
320 neg_accessor(art))
321
322 def testIndexOrLexicalList_ApprovalApprover(self):
323 art = art = fake.MakeTestIssue(789, 1, 'sum 2', 'New', 111)
324 art.labels = ['samename-approver-value1']
325 art.approval_values = [
326 tracker_pb2.ApprovalValue(approval_id=4, approver_ids=[333])]
327
328 all_field_defs = [
329 tracker_bizobj.MakeFieldDef(
330 4, 788, 'samename', tracker_pb2.FieldTypes.APPROVAL_TYPE,
331 None, None, False, False, False, None, None, None, False, None,
332 None, None, None, 'cow spots', False)
333 ]
334 users_by_id = {333: framework_views.StuffUserView(333, 'a@test.com', True)}
335
336 accessor = sorting._IndexOrLexicalList(
337 [], all_field_defs, 'samename-approver', users_by_id)
338 self.assertEqual(['a@test.com', 'value1'], accessor(art))
339 neg_accessor = MakeDescending(accessor)
340 self.assertEqual([sorting.DescendingValue('value1'),
341 sorting.DescendingValue('a@test.com')],
342 neg_accessor(art))
343
344 def testComputeSortDirectives(self):
345 config = tracker_pb2.ProjectIssueConfig()
346 self.assertEqual(
347 ['project', 'id'], sorting.ComputeSortDirectives(config, '', ''))
348
349 self.assertEqual(
350 ['a', 'b', 'c', 'project', 'id'],
351 sorting.ComputeSortDirectives(config, '', 'a b C'))
352
353 config.default_sort_spec = 'id -reporter Owner'
354 self.assertEqual(
355 ['id', '-reporter', 'owner', 'project'],
356 sorting.ComputeSortDirectives(config, '', ''))
357
358 self.assertEqual(
359 ['x', '-b', 'a', 'c', '-owner', 'id', '-reporter', 'project'],
360 sorting.ComputeSortDirectives(config, 'x -b', 'A -b c -owner'))