1# Copyright 2015 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5# pylint: disable=unused-argument
6
7from catapult_base import cloud_storage
8import mock
9from pyfakefs import fake_filesystem_unittest
10
11import dependency_manager
12from dependency_manager import exceptions
13
14
15class DependencyManagerTest(fake_filesystem_unittest.TestCase):
16
17  def setUp(self):
18    self.lp_info012 = dependency_manager.LocalPathInfo(
19        ['path0', 'path1', 'path2'])
20    self.cloud_storage_info = dependency_manager.CloudStorageInfo(
21        'cs_bucket', 'cs_hash', 'download_path', 'cs_remote_path')
22
23    self.dep_info = dependency_manager.DependencyInfo(
24        'dep', 'platform', 'config_file', local_path_info=self.lp_info012,
25        cloud_storage_info=self.cloud_storage_info)
26    self.setUpPyfakefs()
27
28  def tearDown(self):
29    self.tearDownPyfakefs()
30
31  # TODO(nednguyen): add a test that construct
32  # dependency_manager.DependencyManager from a list of DependencyInfo.
33  def testErrorInit(self):
34    with self.assertRaises(ValueError):
35      dependency_manager.DependencyManager(None)
36    with self.assertRaises(ValueError):
37      dependency_manager.DependencyManager('config_file?')
38
39  def testInitialUpdateDependencies(self):
40    dep_manager = dependency_manager.DependencyManager([])
41
42    # Empty BaseConfig.
43    dep_manager._lookup_dict = {}
44    base_config_mock = mock.MagicMock(spec=dependency_manager.BaseConfig)
45    base_config_mock.IterDependencyInfo.return_value = iter([])
46    dep_manager._UpdateDependencies(base_config_mock)
47    self.assertFalse(dep_manager._lookup_dict)
48
49    # One dependency/platform in a BaseConfig.
50    dep_manager._lookup_dict = {}
51    base_config_mock = mock.MagicMock(spec=dependency_manager.BaseConfig)
52    dep_info = mock.MagicMock(spec=dependency_manager.DependencyInfo)
53    dep = 'dependency'
54    plat = 'platform'
55    dep_info.dependency = dep
56    dep_info.platform = plat
57    base_config_mock.IterDependencyInfo.return_value = iter([dep_info])
58    expected_lookup_dict = {dep: {plat: dep_info}}
59    dep_manager._UpdateDependencies(base_config_mock)
60    self.assertEqual(expected_lookup_dict, dep_manager._lookup_dict)
61    self.assertFalse(dep_info.Update.called)
62
63    # One dependency multiple platforms in a BaseConfig.
64    dep_manager._lookup_dict = {}
65    base_config_mock = mock.MagicMock(spec=dependency_manager.BaseConfig)
66    dep = 'dependency'
67    plat1 = 'platform1'
68    plat2 = 'platform2'
69    dep_info1 = mock.MagicMock(spec=dependency_manager.DependencyInfo)
70    dep_info1.dependency = dep
71    dep_info1.platform = plat1
72    dep_info2 = mock.MagicMock(spec=dependency_manager.DependencyInfo)
73    dep_info2.dependency = dep
74    dep_info2.platform = plat2
75    base_config_mock.IterDependencyInfo.return_value = iter([dep_info1,
76                                                             dep_info2])
77    expected_lookup_dict = {dep: {plat1: dep_info1,
78                                  plat2: dep_info2}}
79    dep_manager._UpdateDependencies(base_config_mock)
80    self.assertEqual(expected_lookup_dict, dep_manager._lookup_dict)
81    self.assertFalse(dep_info1.Update.called)
82    self.assertFalse(dep_info2.Update.called)
83
84    # Multiple dependencies, multiple platforms in a BaseConfig.
85    dep_manager._lookup_dict = {}
86    base_config_mock = mock.MagicMock(spec=dependency_manager.BaseConfig)
87    dep1 = 'dependency1'
88    dep2 = 'dependency2'
89    plat1 = 'platform1'
90    plat2 = 'platform2'
91    dep_info1 = mock.MagicMock(spec=dependency_manager.DependencyInfo)
92    dep_info1.dependency = dep1
93    dep_info1.platform = plat1
94    dep_info2 = mock.MagicMock(spec=dependency_manager.DependencyInfo)
95    dep_info2.dependency = dep1
96    dep_info2.platform = plat2
97    dep_info3 = mock.MagicMock(spec=dependency_manager.DependencyInfo)
98    dep_info3.dependency = dep2
99    dep_info3.platform = plat2
100    base_config_mock.IterDependencyInfo.return_value = iter(
101        [dep_info1, dep_info2, dep_info3])
102    expected_lookup_dict = {dep1: {plat1: dep_info1,
103                                   plat2: dep_info2},
104                            dep2: {plat2: dep_info3}}
105    dep_manager._UpdateDependencies(base_config_mock)
106    self.assertEqual(expected_lookup_dict, dep_manager._lookup_dict)
107    self.assertFalse(dep_info1.Update.called)
108    self.assertFalse(dep_info2.Update.called)
109    self.assertFalse(dep_info3.Update.called)
110
111  def testFollowupUpdateDependenciesNoOverlap(self):
112    dep_manager = dependency_manager.DependencyManager([])
113    dep = 'dependency'
114    dep1 = 'dependency1'
115    dep2 = 'dependency2'
116    dep3 = 'dependency3'
117    plat1 = 'platform1'
118    plat2 = 'platform2'
119    plat3 = 'platform3'
120    dep_info_a = mock.MagicMock(spec=dependency_manager.DependencyInfo)
121    dep_info_a.dependency = dep1
122    dep_info_a.platform = plat1
123    dep_info_b = mock.MagicMock(spec=dependency_manager.DependencyInfo)
124    dep_info_b.dependency = dep1
125    dep_info_b.platform = plat2
126    dep_info_c = mock.MagicMock(spec=dependency_manager.DependencyInfo)
127    dep_info_c.dependency = dep
128    dep_info_c.platform = plat1
129
130    start_lookup_dict = {dep: {plat1: dep_info_a,
131                               plat2: dep_info_b},
132                         dep1: {plat1: dep_info_c}}
133    base_config_mock = mock.MagicMock(spec=dependency_manager.BaseConfig)
134
135    # Empty BaseConfig.
136    dep_manager._lookup_dict = start_lookup_dict.copy()
137    base_config_mock.IterDependencyInfo.return_value = iter([])
138    dep_manager._UpdateDependencies(base_config_mock)
139    self.assertEqual(start_lookup_dict, dep_manager._lookup_dict)
140
141    # One dependency/platform in a BaseConfig.
142    dep_manager._lookup_dict = start_lookup_dict.copy()
143    dep_info = mock.MagicMock(spec=dependency_manager.DependencyInfo)
144    dep_info.dependency = dep3
145    dep_info.platform = plat1
146    base_config_mock.IterDependencyInfo.return_value = iter([dep_info])
147    expected_lookup_dict = {dep: {plat1: dep_info_a,
148                                  plat2: dep_info_b},
149                            dep1: {plat1: dep_info_c},
150                            dep3: {plat3: dep_info}}
151
152    dep_manager._UpdateDependencies(base_config_mock)
153    self.assertItemsEqual(expected_lookup_dict, dep_manager._lookup_dict)
154    self.assertFalse(dep_info.Update.called)
155    self.assertFalse(dep_info_a.Update.called)
156    self.assertFalse(dep_info_b.Update.called)
157    self.assertFalse(dep_info_c.Update.called)
158
159    # One dependency multiple platforms in a BaseConfig.
160    dep_manager._lookup_dict = start_lookup_dict.copy()
161    dep_info1 = mock.MagicMock(spec=dependency_manager.DependencyInfo)
162    dep_info1.dependency = dep2
163    dep_info1.platform = plat1
164    dep_info2 = mock.MagicMock(spec=dependency_manager.DependencyInfo)
165    dep_info2.dependency = dep2
166    dep_info2.platform = plat2
167    base_config_mock.IterDependencyInfo.return_value = iter([dep_info1,
168                                                             dep_info2])
169    expected_lookup_dict = {dep: {plat1: dep_info_a,
170                                  plat2: dep_info_b},
171                            dep1: {plat1: dep_info_c},
172                            dep2: {plat1: dep_info1,
173                                   plat2: dep_info2}}
174    dep_manager._UpdateDependencies(base_config_mock)
175    self.assertEqual(expected_lookup_dict, dep_manager._lookup_dict)
176    self.assertFalse(dep_info1.Update.called)
177    self.assertFalse(dep_info2.Update.called)
178    self.assertFalse(dep_info_a.Update.called)
179    self.assertFalse(dep_info_b.Update.called)
180    self.assertFalse(dep_info_c.Update.called)
181
182    # Multiple dependencies, multiple platforms in a BaseConfig.
183    dep_manager._lookup_dict = start_lookup_dict.copy()
184    dep1 = 'dependency1'
185    plat1 = 'platform1'
186    plat2 = 'platform2'
187    dep_info1 = mock.MagicMock(spec=dependency_manager.DependencyInfo)
188    dep_info1.dependency = dep2
189    dep_info1.platform = plat1
190    dep_info2 = mock.MagicMock(spec=dependency_manager.DependencyInfo)
191    dep_info2.dependency = dep2
192    dep_info2.platform = plat2
193    dep_info3 = mock.MagicMock(spec=dependency_manager.DependencyInfo)
194    dep_info3.dependency = dep3
195    dep_info3.platform = plat2
196    base_config_mock.IterDependencyInfo.return_value = iter(
197        [dep_info1, dep_info2, dep_info3])
198    expected_lookup_dict = {dep: {plat1: dep_info_a,
199                                  plat2: dep_info_b},
200                            dep1: {plat1: dep_info_c},
201                            dep2: {plat1: dep_info1,
202                                   plat2: dep_info2},
203                            dep3: {plat2: dep_info3}}
204    dep_manager._UpdateDependencies(base_config_mock)
205    self.assertEqual(expected_lookup_dict, dep_manager._lookup_dict)
206    self.assertFalse(dep_info1.Update.called)
207    self.assertFalse(dep_info2.Update.called)
208    self.assertFalse(dep_info3.Update.called)
209    self.assertFalse(dep_info_a.Update.called)
210    self.assertFalse(dep_info_b.Update.called)
211    self.assertFalse(dep_info_c.Update.called)
212
213    # Ensure the testing data wasn't corrupted.
214    self.assertEqual(start_lookup_dict,
215                     {dep: {plat1: dep_info_a,
216                            plat2: dep_info_b},
217                      dep1: {plat1: dep_info_c}})
218
219  def testFollowupUpdateDependenciesWithCollisions(self):
220    dep_manager = dependency_manager.DependencyManager([])
221    dep = 'dependency'
222    dep1 = 'dependency1'
223    dep2 = 'dependency2'
224    plat1 = 'platform1'
225    plat2 = 'platform2'
226    dep_info_a = mock.MagicMock(spec=dependency_manager.DependencyInfo)
227    dep_info_a.dependency = dep1
228    dep_info_a.platform = plat1
229    dep_info_b = mock.MagicMock(spec=dependency_manager.DependencyInfo)
230    dep_info_b.dependency = dep1
231    dep_info_b.platform = plat2
232    dep_info_c = mock.MagicMock(spec=dependency_manager.DependencyInfo)
233    dep_info_c.dependency = dep
234    dep_info_c.platform = plat1
235
236    start_lookup_dict = {dep: {plat1: dep_info_a,
237                               plat2: dep_info_b},
238                         dep1: {plat1: dep_info_c}}
239    base_config_mock = mock.MagicMock(spec=dependency_manager.BaseConfig)
240
241    # One dependency/platform.
242    dep_manager._lookup_dict = start_lookup_dict.copy()
243    dep_info = mock.MagicMock(spec=dependency_manager.DependencyInfo)
244    dep_info.dependency = dep
245    dep_info.platform = plat1
246    base_config_mock.IterDependencyInfo.return_value = iter([dep_info])
247    expected_lookup_dict = {dep: {plat1: dep_info_a,
248                                  plat2: dep_info_b},
249                            dep1: {plat1: dep_info_c}}
250
251    dep_manager._UpdateDependencies(base_config_mock)
252    self.assertItemsEqual(expected_lookup_dict, dep_manager._lookup_dict)
253    dep_info_a.Update.assert_called_once_with(dep_info)
254    self.assertFalse(dep_info.Update.called)
255    self.assertFalse(dep_info_b.Update.called)
256    self.assertFalse(dep_info_c.Update.called)
257    dep_info_a.reset_mock()
258    dep_info_b.reset_mock()
259    dep_info_c.reset_mock()
260
261    # One dependency multiple platforms in a BaseConfig.
262    dep_manager._lookup_dict = start_lookup_dict.copy()
263    dep_info1 = mock.MagicMock(spec=dependency_manager.DependencyInfo)
264    dep_info1.dependency = dep1
265    dep_info1.platform = plat1
266    dep_info2 = mock.MagicMock(spec=dependency_manager.DependencyInfo)
267    dep_info2.dependency = dep2
268    dep_info2.platform = plat2
269    base_config_mock.IterDependencyInfo.return_value = iter([dep_info1,
270                                                             dep_info2])
271    expected_lookup_dict = {dep: {plat1: dep_info_a,
272                                  plat2: dep_info_b},
273                            dep1: {plat1: dep_info_c},
274                            dep2: {plat2: dep_info2}}
275    dep_manager._UpdateDependencies(base_config_mock)
276    self.assertEqual(expected_lookup_dict, dep_manager._lookup_dict)
277    self.assertFalse(dep_info1.Update.called)
278    self.assertFalse(dep_info2.Update.called)
279    self.assertFalse(dep_info_a.Update.called)
280    self.assertFalse(dep_info_b.Update.called)
281    dep_info_c.Update.assert_called_once_with(dep_info1)
282    dep_info_a.reset_mock()
283    dep_info_b.reset_mock()
284    dep_info_c.reset_mock()
285
286    # Multiple dependencies, multiple platforms in a BaseConfig.
287    dep_manager._lookup_dict = start_lookup_dict.copy()
288    dep1 = 'dependency1'
289    plat1 = 'platform1'
290    plat2 = 'platform2'
291    dep_info1 = mock.MagicMock(spec=dependency_manager.DependencyInfo)
292    dep_info1.dependency = dep
293    dep_info1.platform = plat1
294    dep_info2 = mock.MagicMock(spec=dependency_manager.DependencyInfo)
295    dep_info2.dependency = dep1
296    dep_info2.platform = plat1
297    dep_info3 = mock.MagicMock(spec=dependency_manager.DependencyInfo)
298    dep_info3.dependency = dep2
299    dep_info3.platform = plat2
300    base_config_mock.IterDependencyInfo.return_value = iter(
301        [dep_info1, dep_info2, dep_info3])
302    expected_lookup_dict = {dep: {plat1: dep_info_a,
303                                  plat2: dep_info_b},
304                            dep1: {plat1: dep_info_c},
305                            dep2: {plat2: dep_info3}}
306    dep_manager._UpdateDependencies(base_config_mock)
307    self.assertEqual(expected_lookup_dict, dep_manager._lookup_dict)
308    self.assertFalse(dep_info1.Update.called)
309    self.assertFalse(dep_info2.Update.called)
310    self.assertFalse(dep_info3.Update.called)
311    self.assertFalse(dep_info_b.Update.called)
312    dep_info_a.Update.assert_called_once_with(dep_info1)
313    dep_info_c.Update.assert_called_once_with(dep_info2)
314
315    # Collision error.
316    dep_manager._lookup_dict = start_lookup_dict.copy()
317    dep_info = mock.MagicMock(spec=dependency_manager.DependencyInfo)
318    dep_info.dependency = dep
319    dep_info.platform = plat1
320    base_config_mock.IterDependencyInfo.return_value = iter([dep_info])
321    dep_info_a.Update.side_effect = ValueError
322    self.assertRaises(ValueError,
323                      dep_manager._UpdateDependencies, base_config_mock)
324
325    # Ensure the testing data wasn't corrupted.
326    self.assertEqual(start_lookup_dict,
327                     {dep: {plat1: dep_info_a,
328                            plat2: dep_info_b},
329                      dep1: {plat1: dep_info_c}})
330
331  def testGetDependencyInfo(self):
332    dep_manager = dependency_manager.DependencyManager([])
333    self.assertFalse(dep_manager._lookup_dict)
334
335    # No dependencies in the dependency manager.
336    self.assertEqual(None, dep_manager._GetDependencyInfo('missing_dep',
337                                                          'missing_plat'))
338
339    dep_manager._lookup_dict = {'dep1': {'plat1': 'dep_info11',
340                                         'plat2': 'dep_info12',
341                                         'plat3': 'dep_info13'},
342                                'dep2': {'plat1': 'dep_info11',
343                                         'plat2': 'dep_info21',
344                                         'plat3': 'dep_info23',
345                                         'default': 'dep_info2d'},
346                                'dep3': {'plat1': 'dep_info31',
347                                         'plat2': 'dep_info32',
348                                         'default': 'dep_info3d'}}
349    # Dependency not in the dependency manager.
350    self.assertEqual(None, dep_manager._GetDependencyInfo(
351        'missing_dep', 'missing_plat'))
352    # Dependency in the dependency manager, but not the platform. No default.
353    self.assertEqual(None, dep_manager._GetDependencyInfo(
354        'dep1', 'missing_plat'))
355    # Dependency in the dependency manager, but not the platform, but a default
356    # exists.
357    self.assertEqual('dep_info2d', dep_manager._GetDependencyInfo(
358        'dep2', 'missing_plat'))
359    # Dependency and platform in the dependency manager. A default exists.
360    self.assertEqual('dep_info23', dep_manager._GetDependencyInfo(
361        'dep2', 'plat3'))
362    # Dependency and platform in the dependency manager. No default exists.
363    self.assertEqual('dep_info12', dep_manager._GetDependencyInfo(
364        'dep1', 'plat2'))
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383  @mock.patch(
384      'dependency_manager.dependency_info.DependencyInfo.GetRemotePath')  # pylint: disable=line-too-long
385  def testFetchPathUnititializedDependency(
386      self, cs_path_mock):
387    dep_manager = dependency_manager.DependencyManager([])
388    self.assertFalse(cs_path_mock.call_args)
389    cs_path = 'cs_path'
390    cs_path_mock.return_value = cs_path
391
392    # Empty lookup_dict
393    with self.assertRaises(exceptions.NoPathFoundError):
394      dep_manager.FetchPath('dep', 'plat_arch_x86')
395
396    # Non-empty lookup dict that doesn't contain the dependency we're looking
397    # for.
398    dep_manager._lookup_dict = {'dep1': mock.MagicMock(),
399                                'dep2': mock.MagicMock()}
400    with self.assertRaises(exceptions.NoPathFoundError):
401      dep_manager.FetchPath('dep', 'plat_arch_x86')
402
403  @mock.patch('os.path')
404  @mock.patch(
405      'dependency_manager.DependencyManager._GetDependencyInfo')
406  @mock.patch(
407      'dependency_manager.dependency_info.DependencyInfo.GetRemotePath')  # pylint: disable=line-too-long
408  def testFetchPathLocalFile(self, cs_path_mock, dep_info_mock, path_mock):
409    dep_manager = dependency_manager.DependencyManager([])
410    self.assertFalse(cs_path_mock.call_args)
411    cs_path = 'cs_path'
412    dep_info = self.dep_info
413    cs_path_mock.return_value = cs_path
414    # The DependencyInfo returned should be passed through to LocalPath.
415    dep_info_mock.return_value = dep_info
416
417    # Non-empty lookup dict that contains the dependency we're looking for.
418    # Local path exists.
419    dep_manager._lookup_dict = {'dep': {'platform' : self.dep_info},
420                                'dep2': mock.MagicMock()}
421    self.fs.CreateFile('path1')
422    found_path = dep_manager.FetchPath('dep', 'platform')
423
424    self.assertEqual('path1', found_path)
425    self.assertFalse(cs_path_mock.call_args)
426
427
428  @mock.patch(
429      'dependency_manager.dependency_info.DependencyInfo.GetRemotePath')  # pylint: disable=line-too-long
430  def testFetchPathRemoteFile(
431      self, cs_path_mock):
432    dep_manager = dependency_manager.DependencyManager([])
433    self.assertFalse(cs_path_mock.call_args)
434    cs_path = 'cs_path'
435    def FakeCSPath():
436      self.fs.CreateFile(cs_path)
437      return cs_path
438    cs_path_mock.side_effect = FakeCSPath
439
440    # Non-empty lookup dict that contains the dependency we're looking for.
441    # Local path doesn't exist, but cloud_storage_path is downloaded.
442    dep_manager._lookup_dict = {'dep': {'platform' : self.dep_info,
443                                        'plat1': mock.MagicMock()},
444                                'dep2': {'plat2': mock.MagicMock()}}
445    found_path = dep_manager.FetchPath('dep', 'platform')
446    self.assertEqual(cs_path, found_path)
447
448
449  @mock.patch(
450      'dependency_manager.dependency_info.DependencyInfo.GetRemotePath')  # pylint: disable=line-too-long
451  def testFetchPathError(
452      self, cs_path_mock):
453    dep_manager = dependency_manager.DependencyManager([])
454    self.assertFalse(cs_path_mock.call_args)
455    cs_path_mock.return_value = None
456    dep_manager._lookup_dict = {'dep': {'platform' : self.dep_info,
457                                        'plat1': mock.MagicMock()},
458                                'dep2': {'plat2': mock.MagicMock()}}
459    # Non-empty lookup dict that contains the dependency we're looking for.
460    # Local path doesn't exist, and cloud_storage path wasn't successfully
461    # found.
462    self.assertRaises(exceptions.NoPathFoundError,
463                      dep_manager.FetchPath, 'dep', 'platform')
464
465    cs_path_mock.side_effect = cloud_storage.CredentialsError
466    self.assertRaises(cloud_storage.CredentialsError,
467                      dep_manager.FetchPath, 'dep', 'platform')
468
469    cs_path_mock.side_effect = cloud_storage.CloudStorageError
470    self.assertRaises(cloud_storage.CloudStorageError,
471                      dep_manager.FetchPath, 'dep', 'platform')
472
473    cs_path_mock.side_effect = cloud_storage.PermissionError
474    self.assertRaises(cloud_storage.PermissionError,
475                      dep_manager.FetchPath, 'dep', 'platform')
476
477  def testLocalPath(self):
478    dep_manager = dependency_manager.DependencyManager([])
479    # Empty lookup_dict
480    with self.assertRaises(exceptions.NoPathFoundError):
481      dep_manager.LocalPath('dep', 'plat')
482
483  def testLocalPathNoDependency(self):
484    # Non-empty lookup dict that doesn't contain the dependency we're looking
485    # for.
486    dep_manager = dependency_manager.DependencyManager([])
487    dep_manager._lookup_dict = {'dep1': mock.MagicMock(),
488                                'dep2': mock.MagicMock()}
489    with self.assertRaises(exceptions.NoPathFoundError):
490      dep_manager.LocalPath('dep', 'plat')
491
492  def testLocalPathExists(self):
493    # Non-empty lookup dict that contains the dependency we're looking for.
494    # Local path exists.
495    dep_manager = dependency_manager.DependencyManager([])
496    dep_manager._lookup_dict = {'dependency' : {'platform': self.dep_info},
497                                'dep1': mock.MagicMock(),
498                                'dep2': mock.MagicMock()}
499    self.fs.CreateFile('path1')
500    found_path = dep_manager.LocalPath('dependency', 'platform')
501
502    self.assertEqual('path1', found_path)
503
504  def testLocalPathMissingPaths(self):
505    # Non-empty lookup dict that contains the dependency we're looking for.
506    # Local path is found but doesn't exist.
507    dep_manager = dependency_manager.DependencyManager([])
508    dep_manager._lookup_dict = {'dependency' : {'platform': self.dep_info},
509                                'dep1': mock.MagicMock(),
510                                'dep2': mock.MagicMock()}
511    self.assertRaises(exceptions.NoPathFoundError,
512                      dep_manager.LocalPath, 'dependency', 'platform')
513
514  def testLocalPathNoPaths(self):
515    # Non-empty lookup dict that contains the dependency we're looking for.
516    # Local path isn't found.
517    dep_manager = dependency_manager.DependencyManager([])
518    dep_info = dependency_manager.DependencyInfo(
519        'dep', 'platform', 'config_file',
520        cloud_storage_info=self.cloud_storage_info)
521    dep_manager._lookup_dict = {'dependency' : {'platform': dep_info},
522                                'dep1': mock.MagicMock(),
523                                'dep2': mock.MagicMock()}
524    self.assertRaises(exceptions.NoPathFoundError,
525                      dep_manager.LocalPath, 'dependency', 'platform')
526
527