• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# Copyright 2019 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Unit tests for updating the LLVM next hash."""
8
9from __future__ import print_function
10
11from collections import namedtuple
12import os
13import subprocess
14import unittest
15import unittest.mock as mock
16
17from failure_modes import FailureModes
18from test_helpers import CreateTemporaryJsonFile
19import llvm_patch_management
20import update_chromeos_llvm_next_hash
21
22# These are unittests; protected access is OK to a point.
23# pylint: disable=protected-access
24
25
26class UpdateLLVMNextHashTest(unittest.TestCase):
27  """Test class for updating 'LLVM_NEXT_HASH' of packages."""
28
29  @mock.patch.object(update_chromeos_llvm_next_hash, 'ChrootRunCommand')
30  def testSucceedsToGetChrootPathForPackage(self, mock_chroot_command):
31    package_chroot_path = '/chroot/path/to/package.ebuild'
32
33    # Emulate ChrootRunCommandWOutput behavior when a chroot path is found for
34    # a valid package.
35    mock_chroot_command.return_value = package_chroot_path
36
37    chroot_path = '/test/chroot/path'
38    package_list = ['new-test/package']
39
40    self.assertEqual(
41        update_chromeos_llvm_next_hash.GetChrootBuildPaths(
42            chroot_path, package_list), [package_chroot_path])
43
44    mock_chroot_command.assert_called_once()
45
46  def testFailedToConvertChrootPathWithInvalidPrefixToSymlinkPath(self):
47    chroot_path = '/path/to/chroot'
48    chroot_file_path = '/src/package.ebuild'
49
50    # Verify the exception is raised when a symlink does not have the prefix
51    # '/mnt/host/source/'.
52    with self.assertRaises(ValueError) as err:
53      update_chromeos_llvm_next_hash._ConvertChrootPathsToSymLinkPaths(
54          chroot_path, [chroot_file_path])
55
56    self.assertEqual(
57        str(err.exception), 'Invalid prefix for the chroot path: '
58        '%s' % chroot_file_path)
59
60  def testSucceedsToConvertChrootPathToSymlinkPath(self):
61    chroot_path = '/path/to/chroot'
62    chroot_file_paths = ['/mnt/host/source/src/package.ebuild']
63
64    expected_symlink_path = '/path/to/chroot/src/package.ebuild'
65
66    self.assertEqual(
67        update_chromeos_llvm_next_hash._ConvertChrootPathsToSymLinkPaths(
68            chroot_path, chroot_file_paths), [expected_symlink_path])
69
70  # Simulate 'os.path.islink' when a path is not a symbolic link.
71  @mock.patch.object(os.path, 'islink', return_value=False)
72  def testFailedToGetEbuildPathFromInvalidSymlink(self, mock_islink):
73    symlink_path = '/symlink/path/src/to/package-r1.ebuild'
74
75    # Verify the exception is raised when the argument is not a symbolic link.
76    with self.assertRaises(ValueError) as err:
77      update_chromeos_llvm_next_hash.GetEbuildPathsFromSymLinkPaths(
78          [symlink_path])
79
80    self.assertEqual(
81        str(err.exception), 'Invalid symlink provided: %s' % symlink_path)
82
83    mock_islink.assert_called_once_with(symlink_path)
84
85  # Simulate 'os.path.islink' when a path is a symbolic link.
86  @mock.patch.object(os.path, 'islink', return_value=True)
87  @mock.patch.object(os.path, 'realpath')
88  def testSucceedsToGetEbuildPathFromValidSymlink(self, mock_realpath,
89                                                  mock_islink):
90
91    symlink_path = '/path/to/chroot/src/package-r1.ebuild'
92
93    abs_path_to_package = '/abs/path/to/src/package.ebuild'
94
95    # Simulate 'os.path.realpath' when a valid path is passed in.
96    mock_realpath.return_value = abs_path_to_package
97
98    expected_resolved_paths = {symlink_path: abs_path_to_package}
99
100    self.assertEqual(
101        update_chromeos_llvm_next_hash.GetEbuildPathsFromSymLinkPaths(
102            [symlink_path]), expected_resolved_paths)
103
104    mock_realpath.assert_called_once_with(symlink_path)
105
106    mock_islink.assert_called_once_with(symlink_path)
107
108  # Simulate behavior of 'os.path.isfile()' when the ebuild path to a package
109  # does not exist.
110  @mock.patch.object(os.path, 'isfile', return_value=False)
111  def testFailedToUpdateLLVMNextHashForInvalidEbuildPath(self, mock_isfile):
112    ebuild_path = '/some/path/to/package.ebuild'
113
114    llvm_hash = 'a123testhash1'
115    llvm_revision = 1000
116
117    # Verify the exception is raised when the ebuild path does not exist.
118    with self.assertRaises(ValueError) as err:
119      update_chromeos_llvm_next_hash.UpdateBuildLLVMNextHash(
120          ebuild_path, llvm_hash, llvm_revision)
121
122    self.assertEqual(
123        str(err.exception), 'Invalid ebuild path provided: %s' % ebuild_path)
124
125    mock_isfile.assert_called_once()
126
127  # Simulate 'os.path.isfile' behavior on a valid ebuild path.
128  @mock.patch.object(os.path, 'isfile', return_value=True)
129  def testFailedToUpdateLLVMNextHash(self, mock_isfile):
130    # Create a temporary file to simulate an ebuild file of a package.
131    with CreateTemporaryJsonFile() as ebuild_file:
132      with open(ebuild_file, 'w') as f:
133        f.write('\n'.join([
134            'First line in the ebuild', 'Second line in the ebuild',
135            'Last line in the ebuild'
136        ]))
137
138      llvm_hash = 'a123testhash1'
139      llvm_revision = 1000
140
141      # Verify the exception is raised when the ebuild file does not have
142      # 'LLVM_NEXT_HASH'.
143      with self.assertRaises(ValueError) as err:
144        update_chromeos_llvm_next_hash.UpdateBuildLLVMNextHash(
145            ebuild_file, llvm_hash, llvm_revision)
146
147      self.assertEqual(str(err.exception), 'Failed to update the LLVM hash.')
148
149    self.assertEqual(mock_isfile.call_count, 2)
150
151  # Simulate 'os.path.isfile' behavior on a valid ebuild path.
152  @mock.patch.object(os.path, 'isfile', return_value=True)
153  # Simulate 'ExecCommandAndCaptureOutput()' when successfully staged the
154  # ebuild file for commit.
155  @mock.patch.object(
156      update_chromeos_llvm_next_hash,
157      'ExecCommandAndCaptureOutput',
158      return_value=None)
159  def testSuccessfullyStageTheEbuildForCommitForLLVMNextHashUpdate(
160      self, mock_stage_commit_command, mock_isfile):
161
162    # Create a temporary file to simulate an ebuild file of a package.
163    with CreateTemporaryJsonFile() as ebuild_file:
164      with open(ebuild_file, 'w') as f:
165        f.write('\n'.join([
166            'First line in the ebuild', 'Second line in the ebuild',
167            'LLVM_NEXT_HASH=\"a12b34c56d78e90\" # r500',
168            'Last line in the ebuild'
169        ]))
170
171      # Updates the ebuild's git hash to 'llvm_hash' and revision to
172      # 'llvm_revision'.
173      llvm_hash = 'a123testhash1'
174      llvm_revision = 1000
175
176      update_chromeos_llvm_next_hash.UpdateBuildLLVMNextHash(
177          ebuild_file, llvm_hash, llvm_revision)
178
179      expected_file_contents = [
180          'First line in the ebuild\n', 'Second line in the ebuild\n',
181          'LLVM_NEXT_HASH=\"a123testhash1\" # r1000\n',
182          'Last line in the ebuild'
183      ]
184
185      # Verify the new file contents of the ebuild file match the expected file
186      # contents.
187      with open(ebuild_file) as new_file:
188        file_contents_as_a_list = [cur_line for cur_line in new_file]
189        self.assertListEqual(file_contents_as_a_list, expected_file_contents)
190
191    self.assertEqual(mock_isfile.call_count, 2)
192
193    mock_stage_commit_command.assert_called_once()
194
195  # Simulate behavior of 'os.path.islink()' when the argument passed in is not a
196  # symbolic link.
197  @mock.patch.object(os.path, 'islink', return_value=False)
198  def testFailedToUprevEbuildForInvalidSymlink(self, mock_islink):
199    symlink_to_uprev = '/symlink/to/package.ebuild'
200
201    # Verify the exception is raised when a symbolic link is not passed in.
202    with self.assertRaises(ValueError) as err:
203      update_chromeos_llvm_next_hash.UprevEbuild(symlink_to_uprev)
204
205    self.assertEqual(
206        str(err.exception), 'Invalid symlink provided: %s' % symlink_to_uprev)
207
208    mock_islink.assert_called_once()
209
210  # Simulate 'os.path.islink' when a symbolic link is passed in.
211  @mock.patch.object(os.path, 'islink', return_value=True)
212  def testFailedToUprevEbuild(self, mock_islink):
213    symlink_to_uprev = '/symlink/to/package.ebuild'
214
215    # Verify the exception is raised when the symlink does not have a revision
216    # number.
217    with self.assertRaises(ValueError) as err:
218      update_chromeos_llvm_next_hash.UprevEbuild(symlink_to_uprev)
219
220    self.assertEqual(str(err.exception), 'Failed to uprev the ebuild.')
221
222    mock_islink.assert_called_once_with(symlink_to_uprev)
223
224  # Simulate 'os.path.islink' when a valid symbolic link is passed in.
225  @mock.patch.object(os.path, 'islink', return_value=True)
226  # Simulate 'os.path.dirname' when returning a path to the directory of a
227  # valid symbolic link.
228  @mock.patch.object(os.path, 'dirname', return_value='/symlink/to')
229  # Simulate 'RunCommandWOutput' when successfully added the upreved symlink
230  # for commit.
231  @mock.patch.object(
232      update_chromeos_llvm_next_hash,
233      'ExecCommandAndCaptureOutput',
234      return_value=None)
235  def testSuccessfullyUprevEbuild(self, mock_command_output, mock_dirname,
236                                  mock_islink):
237
238    symlink_to_uprev = '/symlink/to/package-r1.ebuild'
239
240    update_chromeos_llvm_next_hash.UprevEbuild(symlink_to_uprev)
241
242    mock_islink.assert_called_once_with(symlink_to_uprev)
243
244    mock_dirname.assert_called_once_with(symlink_to_uprev)
245
246    mock_command_output.assert_called_once()
247
248  # Simulate behavior of 'os.path.isdir()' when the path to the repo is not a
249  # directory.
250  @mock.patch.object(os.path, 'isdir', return_value=False)
251  def testFailedToCreateRepoForInvalidDirectoryPath(self, mock_isdir):
252    path_to_repo = '/path/to/repo'
253
254    # The name to use for the repo name.
255    llvm_hash = 'a123testhash1'
256
257    # Verify the exception is raised when provided an invalid directory path.
258    with self.assertRaises(ValueError) as err:
259      update_chromeos_llvm_next_hash._CreateRepo(path_to_repo, llvm_hash)
260
261    self.assertEqual(
262        str(err.exception),
263        'Invalid directory path provided: %s' % path_to_repo)
264
265    mock_isdir.assert_called_once()
266
267  # Simulate 'os.path.isdir' when a valid repo path is provided.
268  @mock.patch.object(os.path, 'isdir', return_value=True)
269  # Simulate behavior of 'ExecCommandAndCaptureOutput()' when successfully reset
270  # changes and created a repo.
271  @mock.patch.object(
272      update_chromeos_llvm_next_hash,
273      'ExecCommandAndCaptureOutput',
274      return_value=None)
275  def testSuccessfullyCreatedRepo(self, mock_command_output, mock_isdir):
276    path_to_repo = '/path/to/repo'
277
278    # The name to use for the repo name.
279    llvm_hash = 'a123testhash1'
280
281    update_chromeos_llvm_next_hash._CreateRepo(path_to_repo, llvm_hash)
282
283    mock_isdir.assert_called_once_with(path_to_repo)
284
285    self.assertEqual(mock_command_output.call_count, 2)
286
287  # Simulate behavior of 'os.path.isdir()' when the path to the repo is not a
288  # directory.
289  @mock.patch.object(os.path, 'isdir', return_value=False)
290  def testFailedToDeleteRepoForInvalidDirectoryPath(self, mock_isdir):
291    path_to_repo = '/some/path/to/repo'
292
293    # The name to use for the repo name.
294    llvm_hash = 'a123testhash2'
295
296    # Verify the exception is raised on an invalid repo path.
297    with self.assertRaises(ValueError) as err:
298      update_chromeos_llvm_next_hash._DeleteRepo(path_to_repo, llvm_hash)
299
300    self.assertEqual(
301        str(err.exception),
302        'Invalid directory path provided: %s' % path_to_repo)
303
304    mock_isdir.assert_called_once()
305
306  # Simulate 'os.path.isdir' on valid directory path.
307  @mock.patch.object(os.path, 'isdir', return_value=True)
308  # Simulate 'ExecCommandAndCaptureOutput()' when successfully checkout to
309  # cros/master, reset changes, and deleted the repo.
310  @mock.patch.object(
311      update_chromeos_llvm_next_hash,
312      'ExecCommandAndCaptureOutput',
313      return_value=None)
314  def testSuccessfullyDeletedRepo(self, mock_command_output, mock_isdir):
315    path_to_repo = '/some/path/to/repo'
316
317    # The name of the repo to be deleted.
318    llvm_hash = 'a123testhash2'
319
320    update_chromeos_llvm_next_hash._DeleteRepo(path_to_repo, llvm_hash)
321
322    mock_isdir.assert_called_once_with(path_to_repo)
323
324    self.assertEqual(mock_command_output.call_count, 3)
325
326  def testFailedToFindChangeListURL(self):
327    repo_upload_contents = 'remote:   https://some_url'
328
329    # Verify the exception is raised when failed to find the Gerrit URL when
330    # parsing the 'repo upload' contents.
331    with self.assertRaises(ValueError) as err:
332      update_chromeos_llvm_next_hash.GetGerritRepoUploadContents(
333          repo_upload_contents)
334
335    self.assertEqual(str(err.exception), 'Failed to find change list URL.')
336
337  def testSuccessfullyGetGerritRepoUploadContents(self):
338    repo_upload_contents = ('remote:   https://chromium-review.googlesource.com'
339                            '/c/chromiumos/overlays/chromiumos-overlay/+/'
340                            '193147 Some commit header')
341
342    change_list = update_chromeos_llvm_next_hash.GetGerritRepoUploadContents(
343        repo_upload_contents)
344
345    self.assertEqual(
346        change_list.url,
347        'https://chromium-review.googlesource.com/c/chromiumos/overlays/'
348        'chromiumos-overlay/+/193147')
349
350    self.assertEqual(change_list.cl_number, 193147)
351
352  # Simulate behavior of 'os.path.isdir()' when the path to the repo is not a
353  # directory.
354  @mock.patch.object(os.path, 'isdir', return_value=False)
355  def testFailedToUploadChangesForInvalidPathDirectory(self, mock_isdir):
356    path_to_repo = '/some/path/to/repo'
357
358    # The name of repo to upload for review.
359    llvm_hash = 'a123testhash3'
360
361    # Commit messages to add to the CL.
362    commit_messages = ['-m Test message']
363
364    # Verify exception is raised when on an invalid repo path.
365    with self.assertRaises(ValueError) as err:
366      update_chromeos_llvm_next_hash.UploadChanges(path_to_repo, llvm_hash,
367                                                   commit_messages)
368
369    self.assertEqual(
370        str(err.exception),
371        'Invalid directory path provided: %s' % path_to_repo)
372
373    mock_isdir.assert_called_once()
374
375  # Simulate 'os.path.isdir' on a valid repo path.
376  @mock.patch.object(os.path, 'isdir', return_value=True)
377  # Simulate behavior of 'ExecCommandAndCaptureOutput()' when successfully
378  # committed the changes.
379  @mock.patch.object(
380      update_chromeos_llvm_next_hash,
381      'ExecCommandAndCaptureOutput',
382      return_value=None)
383  @mock.patch.object(subprocess, 'Popen')
384  def testFailedToUploadChangesForReview(self, mock_repo_upload,
385                                         mock_command_output, mock_isdir):
386
387    # Simulate the behavior of 'subprocess.Popen()' when uploading the changes
388    # for review
389    #
390    # `Popen.communicate()` returns a tuple of `stdout` and `stderr`.
391    mock_repo_upload.return_value.communicate.return_value = (
392        None, 'Branch does not exist.')
393
394    # Exit code of 1 means failed to upload changes for review.
395    mock_repo_upload.return_value.returncode = 1
396
397    path_to_repo = '/some/path/to/repo'
398
399    # The name of repo to upload for review.
400    llvm_hash = 'a123testhash3'
401
402    # Commit messages to add to the CL.
403    commit_messages = ['-m Test message']
404
405    # Verify exception is raised when failed to upload the changes for review.
406    with self.assertRaises(ValueError) as err:
407      update_chromeos_llvm_next_hash.UploadChanges(path_to_repo, llvm_hash,
408                                                   commit_messages)
409
410    self.assertEqual(str(err.exception), 'Failed to upload changes for review')
411
412    mock_isdir.assert_called_once_with(path_to_repo)
413
414    mock_command_output.assert_called_once()
415
416    mock_repo_upload.assert_called_once()
417
418  # Simulate 'os.path.isdir' when a valid repo path is passed in.
419  @mock.patch.object(os.path, 'isdir', return_value=True)
420  # Simulate behavior of 'ExecCommandAndCaptureOutput()' when successfully
421  # committed the changes.
422  @mock.patch.object(
423      update_chromeos_llvm_next_hash,
424      'ExecCommandAndCaptureOutput',
425      return_value=None)
426  @mock.patch.object(subprocess, 'Popen')
427  def testSuccessfullyUploadedChangesForReview(self, mock_repo_upload,
428                                               mock_command_output, mock_isdir):
429
430    # A test CL generated by `repo upload`.
431    repo_upload_contents = ('remote: https://chromium-review.googlesource.'
432                            'com/c/chromiumos/overlays/chromiumos-overlay/'
433                            '+/193147 Fix stdout')
434
435    # Simulate the behavior of 'subprocess.Popen()' when uploading the changes
436    # for review
437    #
438    # `Popen.communicate()` returns a tuple of `stdout` and `stderr`.
439    mock_repo_upload.return_value.communicate.return_value = (
440        repo_upload_contents, None)
441
442    # Exit code of 0 means successfully uploaded changes for review.
443    mock_repo_upload.return_value.returncode = 0
444
445    path_to_repo = '/some/path/to/repo'
446
447    # The name of the hash to upload for review.
448    llvm_hash = 'a123testhash3'
449
450    # Commit messages to add to the CL.
451    commit_messages = ['-m Test message']
452
453    change_list = update_chromeos_llvm_next_hash.UploadChanges(
454        path_to_repo, llvm_hash, commit_messages)
455
456    self.assertEqual(
457        change_list.url,
458        'https://chromium-review.googlesource.com/c/chromiumos/overlays/'
459        'chromiumos-overlay/+/193147')
460
461    self.assertEqual(change_list.cl_number, 193147)
462
463    mock_isdir.assert_called_once_with(path_to_repo)
464
465    mock_command_output.assert_called_once()
466
467    mock_repo_upload.assert_called_once()
468
469  @mock.patch.object(update_chromeos_llvm_next_hash, 'GetChrootBuildPaths')
470  @mock.patch.object(update_chromeos_llvm_next_hash,
471                     '_ConvertChrootPathsToSymLinkPaths')
472  def testExceptionRaisedWhenCreatingPathDictionaryFromPackages(
473      self, mock_chroot_paths_to_symlinks, mock_get_chroot_paths):
474
475    chroot_path = '/some/path/to/chroot'
476
477    package_name = 'test-pckg/package'
478    package_chroot_path = '/some/chroot/path/to/package-r1.ebuild'
479
480    # Test function to simulate '_ConvertChrootPathsToSymLinkPaths' when a
481    # symlink does not start with the prefix '/mnt/host/source'.
482    def BadPrefixChrootPath(_chroot_path, _chroot_file_paths):
483      raise ValueError('Invalid prefix for the chroot path: '
484                       '%s' % package_chroot_path)
485
486    # Simulate 'GetChrootBuildPaths' when valid packages are passed in.
487    #
488    # Returns a list of chroot paths.
489    mock_get_chroot_paths.return_value = [package_chroot_path]
490
491    # Use test function to simulate '_ConvertChrootPathsToSymLinkPaths'
492    # behavior.
493    mock_chroot_paths_to_symlinks.side_effect = BadPrefixChrootPath
494
495    # Verify exception is raised when for an invalid prefix in the symlink.
496    with self.assertRaises(ValueError) as err:
497      update_chromeos_llvm_next_hash.CreatePathDictionaryFromPackages(
498          chroot_path, [package_name])
499
500    self.assertEqual(
501        str(err.exception), 'Invalid prefix for the chroot path: '
502        '%s' % package_chroot_path)
503
504    mock_get_chroot_paths.assert_called_once_with(chroot_path, [package_name])
505
506    mock_chroot_paths_to_symlinks.assert_called_once_with(
507        chroot_path, [package_chroot_path])
508
509  @mock.patch.object(update_chromeos_llvm_next_hash, 'GetChrootBuildPaths')
510  @mock.patch.object(update_chromeos_llvm_next_hash,
511                     '_ConvertChrootPathsToSymLinkPaths')
512  @mock.patch.object(update_chromeos_llvm_next_hash,
513                     'GetEbuildPathsFromSymLinkPaths')
514  def testSuccessfullyCreatedPathDictionaryFromPackages(
515      self, mock_ebuild_paths_from_symlink_paths, mock_chroot_paths_to_symlinks,
516      mock_get_chroot_paths):
517
518    package_chroot_path = '/mnt/host/source/src/path/to/package-r1.ebuild'
519
520    # Simulate 'GetChrootBuildPaths' when returning a chroot path for a valid
521    # package.
522    #
523    # Returns a list of chroot paths.
524    mock_get_chroot_paths.return_value = [package_chroot_path]
525
526    package_symlink_path = '/some/path/to/chroot/src/path/to/package-r1.ebuild'
527
528    # Simulate '_ConvertChrootPathsToSymLinkPaths' when returning a symlink to
529    # a chroot path that points to a package.
530    #
531    # Returns a list of symlink file paths.
532    mock_chroot_paths_to_symlinks.return_value = [package_symlink_path]
533
534    chroot_package_path = '/some/path/to/chroot/src/path/to/package.ebuild'
535
536    # Simulate 'GetEbuildPathsFromSymlinkPaths' when returning a dictionary of
537    # a symlink that points to an ebuild.
538    #
539    # Returns a dictionary of a symlink and ebuild file path pair
540    # where the key is the absolute path to the symlink of the ebuild file
541    # and the value is the absolute path to the ebuild file of the package.
542    mock_ebuild_paths_from_symlink_paths.return_value = {
543        package_symlink_path: chroot_package_path
544    }
545
546    chroot_path = '/some/path/to/chroot'
547    package_name = 'test-pckg/package'
548
549    self.assertEqual(
550        update_chromeos_llvm_next_hash.CreatePathDictionaryFromPackages(
551            chroot_path, [package_name]),
552        {package_symlink_path: chroot_package_path})
553
554    mock_get_chroot_paths.assert_called_once_with(chroot_path, [package_name])
555
556    mock_chroot_paths_to_symlinks.assert_called_once_with(
557        chroot_path, [package_chroot_path])
558
559    mock_ebuild_paths_from_symlink_paths.assert_called_once_with(
560        [package_symlink_path])
561
562  # Simulate behavior of 'ExecCommandAndCaptureOutput()' when successfully
563  # removed patches.
564  @mock.patch.object(
565      update_chromeos_llvm_next_hash,
566      'ExecCommandAndCaptureOutput',
567      return_value=None)
568  def testSuccessfullyRemovedPatchesFromFilesDir(self, mock_run_cmd):
569    patches_to_remove_list = [
570        '/abs/path/to/filesdir/cherry/fix_output.patch',
571        '/abs/path/to/filesdir/display_results.patch'
572    ]
573
574    update_chromeos_llvm_next_hash.RemovePatchesFromFilesDir(
575        patches_to_remove_list)
576
577    self.assertEqual(mock_run_cmd.call_count, 2)
578
579  # Simulate behavior of 'os.path.isfile()' when the absolute path to the patch
580  # metadata file does not exist.
581  @mock.patch.object(os.path, 'isfile', return_value=False)
582  def testInvalidPatchMetadataFileStagedForCommit(self, mock_isfile):
583    patch_metadata_path = '/abs/path/to/filesdir/PATCHES'
584
585    # Verify the exception is raised when the absolute path to the patch
586    # metadata file does not exist or is not a file.
587    with self.assertRaises(ValueError) as err:
588      update_chromeos_llvm_next_hash.StagePatchMetadataFileForCommit(
589          patch_metadata_path)
590
591    self.assertEqual(
592        str(err.exception), 'Invalid patch metadata file provided: '
593        '%s' % patch_metadata_path)
594
595    mock_isfile.assert_called_once()
596
597  # Simulate the behavior of 'os.path.isfile()' when the absolute path to the
598  # patch metadata file exists.
599  @mock.patch.object(os.path, 'isfile', return_value=True)
600  # Simulate the behavior of 'ExecCommandAndCaptureOutput()' when successfully
601  # staged the patch metadata file for commit.
602  @mock.patch.object(
603      update_chromeos_llvm_next_hash,
604      'ExecCommandAndCaptureOutput',
605      return_value=None)
606  def testSuccessfullyStagedPatchMetadataFileForCommit(self, mock_run_cmd,
607                                                       _mock_isfile):
608
609    patch_metadata_path = '/abs/path/to/filesdir/PATCHES.json'
610
611    update_chromeos_llvm_next_hash.StagePatchMetadataFileForCommit(
612        patch_metadata_path)
613
614    mock_run_cmd.assert_called_once()
615
616  def testNoPatchResultsForCommit(self):
617    package_1_patch_info_dict = {
618        'applied_patches': ['display_results.patch'],
619        'failed_patches': ['fixes_output.patch'],
620        'non_applicable_patches': [],
621        'disabled_patches': [],
622        'removed_patches': [],
623        'modified_metadata': None
624    }
625
626    package_2_patch_info_dict = {
627        'applied_patches': ['redirects_stdout.patch', 'fix_display.patch'],
628        'failed_patches': [],
629        'non_applicable_patches': [],
630        'disabled_patches': [],
631        'removed_patches': [],
632        'modified_metadata': None
633    }
634
635    test_package_info_dict = {
636        'test-packages/package1': package_1_patch_info_dict,
637        'test-packages/package2': package_2_patch_info_dict
638    }
639
640    test_commit_message = ['-m %s' % 'Updated packages']
641
642    self.assertListEqual(
643        update_chromeos_llvm_next_hash.StagePackagesPatchResultsForCommit(
644            test_package_info_dict, test_commit_message), test_commit_message)
645
646  @mock.patch.object(update_chromeos_llvm_next_hash,
647                     'StagePatchMetadataFileForCommit')
648  @mock.patch.object(update_chromeos_llvm_next_hash,
649                     'RemovePatchesFromFilesDir')
650  def testAddedPatchResultsForCommit(self, mock_remove_patches,
651                                     mock_stage_patches_for_commit):
652
653    package_1_patch_info_dict = {
654        'applied_patches': [],
655        'failed_patches': [],
656        'non_applicable_patches': [],
657        'disabled_patches': ['fixes_output.patch'],
658        'removed_patches': [],
659        'modified_metadata': '/abs/path/to/filesdir/PATCHES.json'
660    }
661
662    package_2_patch_info_dict = {
663        'applied_patches': ['fix_display.patch'],
664        'failed_patches': [],
665        'non_applicable_patches': [],
666        'disabled_patches': [],
667        'removed_patches': ['/abs/path/to/filesdir/redirect_stdout.patch'],
668        'modified_metadata': '/abs/path/to/filesdir/PATCHES.json'
669    }
670
671    test_package_info_dict = {
672        'test-packages/package1': package_1_patch_info_dict,
673        'test-packages/package2': package_2_patch_info_dict
674    }
675
676    test_commit_message = ['-m %s' % 'Updated packages']
677
678    expected_commit_messages = [
679        '-m %s' % 'Updated packages',
680        '-m %s' % 'For the package test-packages/package1:',
681        '-m %s' % 'The patch metadata file PATCHES.json was modified',
682        '-m %s' % 'The following patches were disabled:',
683        '-m %s' % 'fixes_output.patch',
684        '-m %s' % 'For the package test-packages/package2:',
685        '-m %s' % 'The patch metadata file PATCHES.json was modified',
686        '-m %s' % 'The following patches were removed:',
687        '-m %s' % 'redirect_stdout.patch'
688    ]
689
690    self.assertListEqual(
691        update_chromeos_llvm_next_hash.StagePackagesPatchResultsForCommit(
692            test_package_info_dict, test_commit_message),
693        expected_commit_messages)
694
695    path_to_removed_patch = '/abs/path/to/filesdir/redirect_stdout.patch'
696
697    mock_remove_patches.assert_called_once_with([path_to_removed_patch])
698
699    self.assertEqual(mock_stage_patches_for_commit.call_count, 2)
700
701  @mock.patch.object(update_chromeos_llvm_next_hash,
702                     'CreatePathDictionaryFromPackages')
703  @mock.patch.object(update_chromeos_llvm_next_hash, '_CreateRepo')
704  @mock.patch.object(update_chromeos_llvm_next_hash, 'UpdateBuildLLVMNextHash')
705  @mock.patch.object(update_chromeos_llvm_next_hash, 'UprevEbuild')
706  @mock.patch.object(update_chromeos_llvm_next_hash, 'UploadChanges')
707  @mock.patch.object(update_chromeos_llvm_next_hash, '_DeleteRepo')
708  def testExceptionRaisedWhenUpdatingPackages(
709      self, mock_delete_repo, mock_upload_changes, mock_uprev_ebuild,
710      mock_update_llvm_next, mock_create_repo, mock_create_path_dict):
711
712    abs_path_to_package = '/some/path/to/chroot/src/path/to/package.ebuild'
713
714    symlink_path_to_package = \
715        '/some/path/to/chroot/src/path/to/package-r1.ebuild'
716
717    path_to_package_dir = '/some/path/to/chroot/src/path/to'
718
719    # Test function to simulate '_CreateRepo' when successfully created the
720    # repo on a valid repo path.
721    def SuccessfullyCreateRepoForChanges(_repo_path, llvm_hash):
722      self.assertEqual(llvm_hash, 'a123testhash4')
723      return
724
725    # Test function to simulate 'UpdateBuildLLVMNextHash' when successfully
726    # updated the ebuild's 'LLVM_NEXT_HASH'.
727    def SuccessfullyUpdatedLLVMNextHash(ebuild_path, llvm_hash, llvm_version):
728      self.assertEqual(ebuild_path, abs_path_to_package)
729      self.assertEqual(llvm_hash, 'a123testhash4')
730      self.assertEqual(llvm_version, 1000)
731      return
732
733    # Test function to simulate 'UprevEbuild' when the symlink to the ebuild
734    # does not have a revision number.
735    def FailedToUprevEbuild(_symlink_path):
736      # Raises a 'ValueError' exception because the symlink did not have have a
737      # revision number.
738      raise ValueError('Failed to uprev the ebuild.')
739
740    # Test function to fail on 'UploadChanges' if the function gets called
741    # when an exception is raised.
742    def ShouldNotExecuteUploadChanges(_repo_path, _llvm_hash, _commit_messages):
743      # Test function should not be called (i.e. execution should resume in the
744      # 'finally' block) because 'UprevEbuild()' raised an
745      # exception.
746      assert False, 'Failed to go to "finally" block ' \
747          'after the exception was raised.'
748
749    test_package_path_dict = {symlink_path_to_package: abs_path_to_package}
750
751    # Simulate behavior of 'CreatePathDictionaryFromPackages()' when
752    # successfully created a dictionary where the key is the absolute path to
753    # the symlink of the package and value is the absolute path to the ebuild of
754    # the package.
755    mock_create_path_dict.return_value = test_package_path_dict
756
757    # Use test function to simulate behavior.
758    mock_create_repo.side_effect = SuccessfullyCreateRepoForChanges
759    mock_update_llvm_next.side_effect = SuccessfullyUpdatedLLVMNextHash
760    mock_uprev_ebuild.side_effect = FailedToUprevEbuild
761    mock_upload_changes.side_effect = ShouldNotExecuteUploadChanges
762
763    packages_to_update = ['test-packages/package1']
764    patch_metadata_file = 'PATCHES.json'
765    llvm_hash = 'a123testhash4'
766    llvm_version = 1000
767    chroot_path = '/some/path/to/chroot'
768    svn_option = 'google3'
769
770    # Verify exception is raised when an exception is thrown within
771    # the 'try' block by UprevEbuild function.
772    with self.assertRaises(ValueError) as err:
773      update_chromeos_llvm_next_hash.UpdatePackages(
774          packages_to_update, llvm_hash, llvm_version, chroot_path,
775          patch_metadata_file, FailureModes.FAIL, svn_option)
776
777    self.assertEqual(str(err.exception), 'Failed to uprev the ebuild.')
778
779    mock_create_path_dict.assert_called_once_with(chroot_path,
780                                                  packages_to_update)
781
782    mock_create_repo.assert_called_once_with(path_to_package_dir, llvm_hash)
783
784    mock_update_llvm_next.assert_called_once_with(abs_path_to_package,
785                                                  llvm_hash, llvm_version)
786
787    mock_uprev_ebuild.assert_called_once_with(symlink_path_to_package)
788
789    mock_upload_changes.assert_not_called()
790
791    mock_delete_repo.assert_called_once_with(path_to_package_dir, llvm_hash)
792
793  @mock.patch.object(update_chromeos_llvm_next_hash,
794                     'CreatePathDictionaryFromPackages')
795  @mock.patch.object(update_chromeos_llvm_next_hash, '_CreateRepo')
796  @mock.patch.object(update_chromeos_llvm_next_hash, 'UpdateBuildLLVMNextHash')
797  @mock.patch.object(update_chromeos_llvm_next_hash, 'UprevEbuild')
798  @mock.patch.object(update_chromeos_llvm_next_hash, 'UploadChanges')
799  @mock.patch.object(update_chromeos_llvm_next_hash, '_DeleteRepo')
800  @mock.patch.object(llvm_patch_management, 'UpdatePackagesPatchMetadataFile')
801  @mock.patch.object(update_chromeos_llvm_next_hash,
802                     'StagePatchMetadataFileForCommit')
803  def testSuccessfullyUpdatedPackages(
804      self, mock_stage_patch_file, mock_update_package_metadata_file,
805      mock_delete_repo, mock_upload_changes, mock_uprev_ebuild,
806      mock_update_llvm_next, mock_create_repo, mock_create_path_dict):
807
808    abs_path_to_package = '/some/path/to/chroot/src/path/to/package.ebuild'
809
810    symlink_path_to_package = \
811        '/some/path/to/chroot/src/path/to/package-r1.ebuild'
812
813    path_to_package_dir = '/some/path/to/chroot/src/path/to'
814
815    # Test function to simulate '_CreateRepo' when successfully created the repo
816    # for the changes to be made to the ebuild files.
817    def SuccessfullyCreateRepoForChanges(_repo_path, llvm_hash):
818      self.assertEqual(llvm_hash, 'a123testhash5')
819      return
820
821    # Test function to simulate 'UploadChanges' after a successfull update of
822    # 'LLVM_NEXT_HASH" of the ebuild file.
823    def SuccessfullyUpdatedLLVMNextHash(ebuild_path, llvm_hash, llvm_version):
824      self.assertEqual(ebuild_path,
825                       '/some/path/to/chroot/src/path/to/package.ebuild')
826      self.assertEqual(llvm_hash, 'a123testhash5')
827      self.assertEqual(llvm_version, 1000)
828      return
829
830    # Test function to simulate 'UprevEbuild' when successfully incremented
831    # the revision number by 1.
832    def SuccessfullyUprevedEbuild(symlink_path):
833      self.assertEqual(symlink_path,
834                       '/some/path/to/chroot/src/path/to/package-r1.ebuild')
835
836      return
837
838    # Test function to simulate 'UpdatePackagesPatchMetadataFile()' when the
839    # patch results contains a disabled patch in 'disable_patches' mode.
840    def RetrievedPatchResults(chroot_path, llvm_version, patch_metadata_file,
841                              packages, mode):
842
843      self.assertEqual(chroot_path, '/some/path/to/chroot')
844      self.assertEqual(llvm_version, 1000)
845      self.assertEqual(patch_metadata_file, 'PATCHES.json')
846      self.assertListEqual(packages, ['path/to'])
847      self.assertEqual(mode, FailureModes.DISABLE_PATCHES)
848
849      PatchInfo = namedtuple('PatchInfo', [
850          'applied_patches', 'failed_patches', 'non_applicable_patches',
851          'disabled_patches', 'removed_patches', 'modified_metadata'
852      ])
853
854      package_patch_info = PatchInfo(
855          applied_patches=['fix_display.patch'],
856          failed_patches=['fix_stdout.patch'],
857          non_applicable_patches=[],
858          disabled_patches=['fix_stdout.patch'],
859          removed_patches=[],
860          modified_metadata='/abs/path/to/filesdir/%s' % patch_metadata_file)
861
862      package_info_dict = {'path/to': package_patch_info._asdict()}
863
864      # Returns a dictionary where the key is the package and the value is a
865      # dictionary that contains information about the package's patch results
866      # produced by the patch manager.
867      return package_info_dict
868
869    # Test function to simulate 'UploadChanges()' when successfully created a
870    # commit for the changes made to the packages and their patches and
871    # retrieved the change list of the commit.
872    def SuccessfullyUploadedChanges(_repo_path, _llvm_hash, _commit_messages):
873      commit_url = 'https://some_name/path/to/commit/+/12345'
874
875      return update_chromeos_llvm_next_hash.CommitContents(
876          url=commit_url, cl_number=12345)
877
878    test_package_path_dict = {symlink_path_to_package: abs_path_to_package}
879
880    # Simulate behavior of 'CreatePathDictionaryFromPackages()' when
881    # successfully created a dictionary where the key is the absolute path to
882    # the symlink of the package and value is the absolute path to the ebuild of
883    # the package.
884    mock_create_path_dict.return_value = test_package_path_dict
885
886    # Use test function to simulate behavior.
887    mock_create_repo.side_effect = SuccessfullyCreateRepoForChanges
888    mock_update_llvm_next.side_effect = SuccessfullyUpdatedLLVMNextHash
889    mock_uprev_ebuild.side_effect = SuccessfullyUprevedEbuild
890    mock_update_package_metadata_file.side_effect = RetrievedPatchResults
891    mock_upload_changes.side_effect = SuccessfullyUploadedChanges
892
893    packages_to_update = ['test-packages/package1']
894    patch_metadata_file = 'PATCHES.json'
895    llvm_hash = 'a123testhash5'
896    llvm_version = 1000
897    chroot_path = '/some/path/to/chroot'
898    svn_option = 'tot'
899
900    change_list = update_chromeos_llvm_next_hash.UpdatePackages(
901        packages_to_update, llvm_hash, llvm_version, chroot_path,
902        patch_metadata_file, FailureModes.DISABLE_PATCHES, svn_option)
903
904    self.assertEqual(change_list.url,
905                     'https://some_name/path/to/commit/+/12345')
906
907    self.assertEqual(change_list.cl_number, 12345)
908
909    mock_create_path_dict.assert_called_once_with(chroot_path,
910                                                  packages_to_update)
911
912    mock_create_repo.assert_called_once_with(path_to_package_dir, llvm_hash)
913
914    mock_update_llvm_next.assert_called_once_with(abs_path_to_package,
915                                                  llvm_hash, llvm_version)
916
917    mock_uprev_ebuild.assert_called_once_with(symlink_path_to_package)
918
919    expected_commit_messages = [
920        '-m %s' % 'llvm-next/tot: upgrade to a123testhash5 (r1000)',
921        '-m %s' % 'The following packages have been updated:',
922        '-m %s' % 'path/to',
923        '-m %s' % 'For the package path/to:',
924        '-m %s' % 'The patch metadata file PATCHES.json was modified',
925        '-m %s' % 'The following patches were disabled:',
926        '-m %s' % 'fix_stdout.patch'
927    ]
928
929    mock_update_package_metadata_file.assert_called_once()
930
931    mock_stage_patch_file.assert_called_once_with(
932        '/abs/path/to/filesdir/PATCHES.json')
933
934    mock_upload_changes.assert_called_once_with(path_to_package_dir, llvm_hash,
935                                                expected_commit_messages)
936
937    mock_delete_repo.assert_called_once_with(path_to_package_dir, llvm_hash)
938
939
940if __name__ == '__main__':
941  unittest.main()
942