OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Unit tests for git_cl.py.""" | 6 """Unit tests for git_cl.py.""" |
7 | 7 |
8 import os | 8 import os |
9 import StringIO | 9 import StringIO |
10 import stat | 10 import stat |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
67 "GERRIT_HOST: True\n") | 67 "GERRIT_HOST: True\n") |
68 | 68 |
69 | 69 |
70 class AuthenticatorMock(object): | 70 class AuthenticatorMock(object): |
71 def __init__(self, *_args): | 71 def __init__(self, *_args): |
72 pass | 72 pass |
73 def has_cached_credentials(self): | 73 def has_cached_credentials(self): |
74 return True | 74 return True |
75 | 75 |
76 | 76 |
| 77 def CookiesAuthenticatorMockFactory(hosts_with_creds=None, same_cookie=False): |
| 78 """Use to mock Gerrit/Git credentials from ~/.netrc or ~/.gitcookies. |
| 79 |
| 80 Usage: |
| 81 >>> self.mock(git_cl.gerrit_util, "CookiesAuthenticator", |
| 82 CookiesAuthenticatorMockFactory({'host1': 'cookie1'})) |
| 83 |
| 84 OR |
| 85 >>> self.mock(git_cl.gerrit_util, "CookiesAuthenticator", |
| 86 CookiesAuthenticatorMockFactory(cookie='cookie')) |
| 87 """ |
| 88 class CookiesAuthenticatorMock(git_cl.gerrit_util.CookiesAuthenticator): |
| 89 def __init__(self): # pylint: disable=W0231 |
| 90 # Intentionally not calling super() because it reads actual cookie files. |
| 91 pass |
| 92 @classmethod |
| 93 def get_gitcookies_path(cls): |
| 94 return '~/.gitcookies' |
| 95 @classmethod |
| 96 def get_netrc_path(cls): |
| 97 return '~/.netrc' |
| 98 def get_auth_header(self, host): |
| 99 if same_cookie: |
| 100 return same_cookie |
| 101 return (hosts_with_creds or {}).get(host) |
| 102 return CookiesAuthenticatorMock |
| 103 |
| 104 |
77 class TestGitClBasic(unittest.TestCase): | 105 class TestGitClBasic(unittest.TestCase): |
78 def _test_ParseIssueUrl(self, func, url, issue, patchset, hostname, fail): | 106 def _test_ParseIssueUrl(self, func, url, issue, patchset, hostname, fail): |
79 parsed = urlparse.urlparse(url) | 107 parsed = urlparse.urlparse(url) |
80 result = func(parsed) | 108 result = func(parsed) |
81 if fail: | 109 if fail: |
82 self.assertIsNone(result) | 110 self.assertIsNone(result) |
83 return None | 111 return None |
84 self.assertIsNotNone(result) | 112 self.assertIsNotNone(result) |
85 self.assertEqual(result.issue, issue) | 113 self.assertEqual(result.issue, issue) |
86 self.assertEqual(result.patchset, patchset) | 114 self.assertEqual(result.patchset, patchset) |
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
184 self._mocked_call(['get_or_create_merge_base']+list(a)))) | 212 self._mocked_call(['get_or_create_merge_base']+list(a)))) |
185 self.mock(git_cl, 'BranchExists', lambda _: True) | 213 self.mock(git_cl, 'BranchExists', lambda _: True) |
186 self.mock(git_cl, 'FindCodereviewSettingsFile', lambda: '') | 214 self.mock(git_cl, 'FindCodereviewSettingsFile', lambda: '') |
187 self.mock(git_cl, 'ask_for_data', self._mocked_call) | 215 self.mock(git_cl, 'ask_for_data', self._mocked_call) |
188 self.mock(git_cl.presubmit_support, 'DoPresubmitChecks', PresubmitMock) | 216 self.mock(git_cl.presubmit_support, 'DoPresubmitChecks', PresubmitMock) |
189 self.mock(git_cl.rietveld, 'Rietveld', RietveldMock) | 217 self.mock(git_cl.rietveld, 'Rietveld', RietveldMock) |
190 self.mock(git_cl.rietveld, 'CachingRietveld', RietveldMock) | 218 self.mock(git_cl.rietveld, 'CachingRietveld', RietveldMock) |
191 self.mock(git_cl.upload, 'RealMain', self.fail) | 219 self.mock(git_cl.upload, 'RealMain', self.fail) |
192 self.mock(git_cl.watchlists, 'Watchlists', WatchlistsMock) | 220 self.mock(git_cl.watchlists, 'Watchlists', WatchlistsMock) |
193 self.mock(git_cl.auth, 'get_authenticator_for_host', AuthenticatorMock) | 221 self.mock(git_cl.auth, 'get_authenticator_for_host', AuthenticatorMock) |
| 222 self.mock(git_cl.gerrit_util.GceAuthenticator, 'is_gce', |
| 223 classmethod(lambda _: False)) |
194 # It's important to reset settings to not have inter-tests interference. | 224 # It's important to reset settings to not have inter-tests interference. |
195 git_cl.settings = None | 225 git_cl.settings = None |
196 | 226 |
197 | 227 |
198 def tearDown(self): | 228 def tearDown(self): |
199 try: | 229 try: |
200 # Note: has_failed returns True if at least 1 test ran so far, current | 230 # Note: has_failed returns True if at least 1 test ran so far, current |
201 # included, has failed. That means current test may have actually ran | 231 # included, has failed. That means current test may have actually ran |
202 # fine, and the check for no leftover calls would be skipped. | 232 # fine, and the check for no leftover calls would be skipped. |
203 if not self.has_failed(): | 233 if not self.has_failed(): |
(...skipping 135 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
339 def _git_post_upload_calls(cls): | 369 def _git_post_upload_calls(cls): |
340 return [ | 370 return [ |
341 ((['git', 'rev-parse', 'HEAD'],), 'hash'), | 371 ((['git', 'rev-parse', 'HEAD'],), 'hash'), |
342 ((['git', 'symbolic-ref', 'HEAD'],), 'hash'), | 372 ((['git', 'symbolic-ref', 'HEAD'],), 'hash'), |
343 ((['git', | 373 ((['git', |
344 'config', 'branch.hash.last-upload-hash', 'hash'],), ''), | 374 'config', 'branch.hash.last-upload-hash', 'hash'],), ''), |
345 ((['git', 'config', 'rietveld.run-post-upload-hook'],), ''), | 375 ((['git', 'config', 'rietveld.run-post-upload-hook'],), ''), |
346 ] | 376 ] |
347 | 377 |
348 @staticmethod | 378 @staticmethod |
349 def _git_sanity_checks(diff_base, working_branch): | 379 def _git_sanity_checks(diff_base, working_branch, get_remote_branch=True): |
350 fake_ancestor = 'fake_ancestor' | 380 fake_ancestor = 'fake_ancestor' |
351 fake_cl = 'fake_cl_for_patch' | 381 fake_cl = 'fake_cl_for_patch' |
352 return [ | 382 return [ |
353 # Calls to verify branch point is ancestor | |
354 ((['git', | 383 ((['git', |
355 'rev-parse', '--verify', diff_base],), fake_ancestor), | 384 'rev-parse', '--verify', diff_base],), fake_ancestor), |
356 ((['git', | 385 ((['git', |
357 'merge-base', fake_ancestor, 'HEAD'],), fake_ancestor), | 386 'merge-base', fake_ancestor, 'HEAD'],), fake_ancestor), |
358 ((['git', | 387 ((['git', |
359 'rev-list', '^' + fake_ancestor, 'HEAD'],), fake_cl), | 388 'rev-list', '^' + fake_ancestor, 'HEAD'],), fake_cl), |
360 # Mock a config miss (error code 1) | 389 # Mock a config miss (error code 1) |
361 ((['git', | 390 ((['git', |
362 'config', 'gitcl.remotebranch'],), (('', None), 1)), | 391 'config', 'gitcl.remotebranch'],), (('', None), 1)), |
| 392 ] + ([ |
363 # Call to GetRemoteBranch() | 393 # Call to GetRemoteBranch() |
364 ((['git', | 394 ((['git', |
365 'config', 'branch.%s.merge' % working_branch],), | 395 'config', 'branch.%s.merge' % working_branch],), |
366 'refs/heads/master'), | 396 'refs/heads/master'), |
367 ((['git', | 397 ((['git', |
368 'config', 'branch.%s.remote' % working_branch],), 'origin'), | 398 'config', 'branch.%s.remote' % working_branch],), 'origin'), |
| 399 ] if get_remote_branch else []) + [ |
369 ((['git', 'rev-list', '^' + fake_ancestor, | 400 ((['git', 'rev-list', '^' + fake_ancestor, |
370 'refs/remotes/origin/master'],), ''), | 401 'refs/remotes/origin/master'],), ''), |
371 ] | 402 ] |
372 | 403 |
373 @classmethod | 404 @classmethod |
374 def _dcommit_calls_1(cls): | 405 def _dcommit_calls_1(cls): |
375 return [ | 406 return [ |
376 ((['git', 'config', 'rietveld.autoupdate'],), | 407 ((['git', 'config', 'rietveld.autoupdate'],), |
377 ''), | 408 ''), |
378 ((['git', 'config', 'rietveld.pending-ref-prefix'],), | 409 ((['git', 'config', 'rietveld.pending-ref-prefix'],), |
379 ''), | 410 ''), |
380 ((['git', | 411 ((['git', |
381 'config', '--local', '--get-regexp', '^svn-remote\\.'],), | 412 'config', '--local', '--get-regexp', '^svn-remote\\.'],), |
(...skipping 256 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
638 | 669 |
639 def test_dcommit_bypass_hooks(self): | 670 def test_dcommit_bypass_hooks(self): |
640 self.calls = ( | 671 self.calls = ( |
641 self._dcommit_calls_1() + | 672 self._dcommit_calls_1() + |
642 self._dcommit_calls_bypassed() + | 673 self._dcommit_calls_bypassed() + |
643 self._dcommit_calls_3()) | 674 self._dcommit_calls_3()) |
644 git_cl.main(['dcommit', '--bypass-hooks']) | 675 git_cl.main(['dcommit', '--bypass-hooks']) |
645 | 676 |
646 | 677 |
647 @classmethod | 678 @classmethod |
| 679 def _gerrit_ensure_auth_calls(cls, issue=None): |
| 680 calls = [] |
| 681 if issue: |
| 682 calls.extend([ |
| 683 ((['git', 'config', 'branch.master.gerritserver'],), ''), |
| 684 ]) |
| 685 calls.extend([ |
| 686 ((['git', 'config', 'branch.master.merge'],), 'refs/heads/master'), |
| 687 ((['git', 'config', 'branch.master.remote'],), 'origin'), |
| 688 ((['git', 'config', 'remote.origin.url'],), |
| 689 'https://chromium.googlesource.com/my/repo'), |
| 690 ((['git', 'config', 'remote.origin.url'],), |
| 691 'https://chromium.googlesource.com/my/repo'), |
| 692 ]) |
| 693 return calls |
| 694 |
| 695 @classmethod |
648 def _gerrit_base_calls(cls, issue=None): | 696 def _gerrit_base_calls(cls, issue=None): |
649 return [ | 697 return [ |
650 ((['git', 'symbolic-ref', 'HEAD'],), 'master'), | 698 ((['git', 'symbolic-ref', 'HEAD'],), 'master'), |
651 ((['git', 'config', '--int', '--get', | 699 ((['git', 'config', '--int', '--get', |
652 'branch.master.git-cl-similarity'],), ''), | 700 'branch.master.git-cl-similarity'],), ''), |
653 ((['git', 'symbolic-ref', 'HEAD'],), 'master'), | 701 ((['git', 'symbolic-ref', 'HEAD'],), 'master'), |
654 ((['git', 'config', '--int', '--get', | 702 ((['git', 'config', '--int', '--get', |
655 'branch.master.git-find-copies'],), ''), | 703 'branch.master.git-find-copies'],), ''), |
656 ] + cls._is_gerrit_calls(True) + [ | 704 ] + cls._is_gerrit_calls(True) + [ |
657 ((['git', 'symbolic-ref', 'HEAD'],), 'master'), | 705 ((['git', 'symbolic-ref', 'HEAD'],), 'master'), |
658 ((['git', 'config', 'branch.master.rietveldissue'],), ''), | 706 ((['git', 'config', 'branch.master.rietveldissue'],), ''), |
659 ((['git', 'config', 'branch.master.gerritissue'],), | 707 ((['git', 'config', 'branch.master.gerritissue'],), |
660 '' if issue is None else str(issue)), | 708 '' if issue is None else str(issue)), |
661 ((['git', 'config', 'branch.master.merge'],), 'master'), | 709 ((['git', 'config', 'branch.master.merge'],), 'refs/heads/master'), |
662 ((['git', 'config', 'branch.master.remote'],), 'origin'), | 710 ((['git', 'config', 'branch.master.remote'],), 'origin'), |
663 ((['get_or_create_merge_base', 'master', 'master'],), | 711 ((['get_or_create_merge_base', 'master', |
| 712 'refs/remotes/origin/master'],), |
664 'fake_ancestor_sha'), | 713 'fake_ancestor_sha'), |
665 ] + cls._git_sanity_checks('fake_ancestor_sha', 'master') + [ | 714 # Calls to verify branch point is ancestor |
| 715 ] + (cls._gerrit_ensure_auth_calls(issue=issue) + |
| 716 cls._git_sanity_checks('fake_ancestor_sha', 'master', |
| 717 get_remote_branch=False)) + [ |
666 ((['git', 'rev-parse', '--show-cdup'],), ''), | 718 ((['git', 'rev-parse', '--show-cdup'],), ''), |
667 ((['git', 'rev-parse', 'HEAD'],), '12345'), | 719 ((['git', 'rev-parse', 'HEAD'],), '12345'), |
| 720 |
668 ((['git', | 721 ((['git', |
669 'diff', '--name-status', '--no-renames', '-r', | 722 'diff', '--name-status', '--no-renames', '-r', |
670 'fake_ancestor_sha...', '.'],), | 723 'fake_ancestor_sha...', '.'],), |
671 'M\t.gitignore\n'), | 724 'M\t.gitignore\n'), |
672 ((['git', 'config', 'branch.master.gerritpatchset'],), ''), | 725 ((['git', 'config', 'branch.master.gerritpatchset'],), ''), |
673 ] + ([] if issue else [ | 726 ] + ([] if issue else [ |
674 ((['git', | 727 ((['git', |
675 'log', '--pretty=format:%s%n%n%b', 'fake_ancestor_sha...'],), | 728 'log', '--pretty=format:%s%n%n%b', 'fake_ancestor_sha...'],), |
676 'foo'), | 729 'foo'), |
677 ]) + [ | 730 ]) + [ |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
723 calls += [ | 776 calls += [ |
724 ((['git', 'config', 'core.editor'],), ''), | 777 ((['git', 'config', 'core.editor'],), ''), |
725 ((['RunEditor'],), description), | 778 ((['RunEditor'],), description), |
726 ] | 779 ] |
727 ref_to_push = 'abcdef0123456789' | 780 ref_to_push = 'abcdef0123456789' |
728 calls += [ | 781 calls += [ |
729 ((['git', 'config', 'branch.master.merge'],), | 782 ((['git', 'config', 'branch.master.merge'],), |
730 'refs/heads/master'), | 783 'refs/heads/master'), |
731 ((['git', 'config', 'branch.master.remote'],), | 784 ((['git', 'config', 'branch.master.remote'],), |
732 'origin'), | 785 'origin'), |
733 ((['get_or_create_merge_base', 'master', 'master'],), | 786 ((['get_or_create_merge_base', 'master', |
| 787 'refs/remotes/origin/master'],), |
734 'origin/master'), | 788 'origin/master'), |
735 ((['git', 'rev-parse', 'HEAD:'],), | 789 ((['git', 'rev-parse', 'HEAD:'],), |
736 '0123456789abcdef'), | 790 '0123456789abcdef'), |
737 ((['git', 'commit-tree', '0123456789abcdef', '-p', | 791 ((['git', 'commit-tree', '0123456789abcdef', '-p', |
738 'origin/master', '-m', description],), | 792 'origin/master', '-m', description],), |
739 ref_to_push), | 793 ref_to_push), |
740 ] | 794 ] |
741 else: | 795 else: |
742 ref_to_push = 'HEAD' | 796 ref_to_push = 'HEAD' |
743 | 797 |
(...skipping 23 matching lines...) Expand all Loading... |
767 'remote:\n' | 821 'remote:\n' |
768 'remote: New Changes:\n' | 822 'remote: New Changes:\n' |
769 'remote: https://chromium-review.googlesource.com/123456 XXX.\n' | 823 'remote: https://chromium-review.googlesource.com/123456 XXX.\n' |
770 'remote:\n' | 824 'remote:\n' |
771 'To https://chromium.googlesource.com/yyy/zzz\n' | 825 'To https://chromium.googlesource.com/yyy/zzz\n' |
772 ' * [new branch] hhhh -> refs/for/refs/heads/master\n')), | 826 ' * [new branch] hhhh -> refs/for/refs/heads/master\n')), |
773 ] | 827 ] |
774 if squash: | 828 if squash: |
775 calls += [ | 829 calls += [ |
776 ((['git', 'config', 'branch.master.gerritissue', '123456'],), ''), | 830 ((['git', 'config', 'branch.master.gerritissue', '123456'],), ''), |
777 ((['git', 'config', 'branch.master.gerritserver'],), ''), | |
778 ((['git', 'config', 'remote.origin.url'],), | |
779 'https://chromium.googlesource.com/my/repo.git'), | |
780 ((['git', 'config', 'branch.master.gerritserver', | 831 ((['git', 'config', 'branch.master.gerritserver', |
781 'https://chromium-review.googlesource.com'],), ''), | 832 'https://chromium-review.googlesource.com'],), ''), |
782 ((['git', 'config', 'branch.master.gerritsquashhash', | 833 ((['git', 'config', 'branch.master.gerritsquashhash', |
783 'abcdef0123456789'],), ''), | 834 'abcdef0123456789'],), ''), |
784 ] | 835 ] |
785 calls += cls._git_post_upload_calls() | 836 calls += cls._git_post_upload_calls() |
786 return calls | 837 return calls |
787 | 838 |
788 def _run_gerrit_upload_test( | 839 def _run_gerrit_upload_test( |
789 self, | 840 self, |
790 upload_args, | 841 upload_args, |
791 description, | 842 description, |
792 reviewers, | 843 reviewers, |
793 squash=False, | 844 squash=False, |
794 expected_upstream_ref='origin/refs/heads/master', | 845 expected_upstream_ref='origin/refs/heads/master', |
795 post_amend_description=None, | 846 post_amend_description=None, |
796 issue=None): | 847 issue=None): |
797 """Generic gerrit upload test framework.""" | 848 """Generic gerrit upload test framework.""" |
| 849 self.mock(git_cl.gerrit_util, "CookiesAuthenticator", |
| 850 CookiesAuthenticatorMockFactory(same_cookie='same_cred')) |
798 self.calls = self._gerrit_base_calls(issue=issue) | 851 self.calls = self._gerrit_base_calls(issue=issue) |
799 self.calls += self._gerrit_upload_calls( | 852 self.calls += self._gerrit_upload_calls( |
800 description, reviewers, squash, | 853 description, reviewers, squash, |
801 expected_upstream_ref=expected_upstream_ref, | 854 expected_upstream_ref=expected_upstream_ref, |
802 post_amend_description=post_amend_description, | 855 post_amend_description=post_amend_description, |
803 issue=issue) | 856 issue=issue) |
804 # Uncomment when debugging. | 857 # Uncomment when debugging. |
805 # print '\n'.join(map(lambda x: '%2i: %s' % x, enumerate(self.calls))) | 858 # print '\n'.join(map(lambda x: '%2i: %s' % x, enumerate(self.calls))) |
806 git_cl.main(['upload'] + upload_args) | 859 git_cl.main(['upload'] + upload_args) |
807 | 860 |
(...skipping 378 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1186 ((['git', 'config', '--local', '--get-regexp', | 1239 ((['git', 'config', '--local', '--get-regexp', |
1187 'branch\\..*\\.rietveldissue'], ), '', | 1240 'branch\\..*\\.rietveldissue'], ), '', |
1188 subprocess2.CalledProcessError(1, '', '', '', '')), | 1241 subprocess2.CalledProcessError(1, '', '', '', '')), |
1189 ((['git', 'config', '--local', '--get-regexp', | 1242 ((['git', 'config', '--local', '--get-regexp', |
1190 'branch\\..*\\.gerritissue'], ), '', | 1243 'branch\\..*\\.gerritissue'], ), '', |
1191 subprocess2.CalledProcessError(1, '', '', '', '')), | 1244 subprocess2.CalledProcessError(1, '', '', '', '')), |
1192 | 1245 |
1193 ] | 1246 ] |
1194 self.assertEqual(1, git_cl.main(['checkout', '99999'])) | 1247 self.assertEqual(1, git_cl.main(['checkout', '99999'])) |
1195 | 1248 |
| 1249 def _test_gerrit_ensure_authenticated_common(self, auth): |
| 1250 self.mock(git_cl.gerrit_util, 'CookiesAuthenticator', |
| 1251 CookiesAuthenticatorMockFactory(hosts_with_creds=auth)) |
| 1252 self.mock(git_cl, 'DieWithError', |
| 1253 lambda msg: self._mocked_call(['DieWithError', msg])) |
| 1254 self.mock(git_cl, 'ask_for_data', |
| 1255 lambda msg: self._mocked_call(['ask_for_data', msg])) |
| 1256 self.calls = [ |
| 1257 ((['git', 'symbolic-ref', 'HEAD'],), 'master') |
| 1258 ] + self._gerrit_ensure_auth_calls() |
| 1259 cl = git_cl.Changelist(codereview='gerrit') |
| 1260 cl.lookedup_issue = True |
| 1261 return cl |
| 1262 |
| 1263 def test_gerrit_ensure_authenticated_missing(self): |
| 1264 cl = self._test_gerrit_ensure_authenticated_common(auth={ |
| 1265 'chromium.googlesource.com': 'git is ok, but gerrit one is missing', |
| 1266 }) |
| 1267 self.calls.append( |
| 1268 ((['DieWithError', |
| 1269 'Credentials for the following hosts are required:\n' |
| 1270 ' chromium-review.googlesource.com\n' |
| 1271 'These are read from ~/.gitcookies (or legacy ~/.netrc)\n' |
| 1272 'You can (re)generate your credentails by visiting ' |
| 1273 'https://chromium-review.googlesource.com/new-password'],), ''),) |
| 1274 self.assertIsNone(cl.EnsureAuthenticated(force=False)) |
| 1275 |
| 1276 def test_gerrit_ensure_authenticated_conflict(self): |
| 1277 cl = self._test_gerrit_ensure_authenticated_common(auth={ |
| 1278 'chromium.googlesource.com': 'one', |
| 1279 'chromium-review.googlesource.com': 'other', |
| 1280 }) |
| 1281 self.calls.append( |
| 1282 ((['ask_for_data', 'If you know what you are doing, ' |
| 1283
'press Enter to continue, Ctrl+C to abort.'],), '')) |
| 1284 self.assertIsNone(cl.EnsureAuthenticated(force=False)) |
| 1285 |
| 1286 def test_gerrit_ensure_authenticated_ok(self): |
| 1287 cl = self._test_gerrit_ensure_authenticated_common(auth={ |
| 1288 'chromium.googlesource.com': 'same', |
| 1289 'chromium-review.googlesource.com': 'same', |
| 1290 }) |
| 1291 self.assertIsNone(cl.EnsureAuthenticated(force=False)) |
| 1292 |
1196 | 1293 |
1197 if __name__ == '__main__': | 1294 if __name__ == '__main__': |
1198 git_cl.logging.basicConfig( | 1295 git_cl.logging.basicConfig( |
1199 level=git_cl.logging.DEBUG if '-v' in sys.argv else git_cl.logging.ERROR) | 1296 level=git_cl.logging.DEBUG if '-v' in sys.argv else git_cl.logging.ERROR) |
1200 unittest.main() | 1297 unittest.main() |
OLD | NEW |