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