git_cl_test.py 134 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754
  1. #!/usr/bin/env vpython3
  2. # coding=utf-8
  3. # Copyright (c) 2012 The Chromium 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. """Unit tests for git_cl.py."""
  7. from __future__ import print_function
  8. from __future__ import unicode_literals
  9. import datetime
  10. import json
  11. import logging
  12. import multiprocessing
  13. import optparse
  14. import os
  15. import pprint
  16. import shutil
  17. import sys
  18. import tempfile
  19. import unittest
  20. if sys.version_info.major == 2:
  21. from StringIO import StringIO
  22. import mock
  23. else:
  24. from io import StringIO
  25. from unittest import mock
  26. sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
  27. import metrics
  28. # We have to disable monitoring before importing git_cl.
  29. metrics.DISABLE_METRICS_COLLECTION = True
  30. import clang_format
  31. import contextlib
  32. import gclient_utils
  33. import gerrit_util
  34. import git_cl
  35. import git_common
  36. import git_footers
  37. import git_new_branch
  38. import scm
  39. import subprocess2
  40. def callError(code=1, cmd='', cwd='', stdout=b'', stderr=b''):
  41. return subprocess2.CalledProcessError(code, cmd, cwd, stdout, stderr)
  42. CERR1 = callError(1)
  43. class TemporaryFileMock(object):
  44. def __init__(self):
  45. self.suffix = 0
  46. @contextlib.contextmanager
  47. def __call__(self):
  48. self.suffix += 1
  49. yield '/tmp/fake-temp' + str(self.suffix)
  50. class ChangelistMock(object):
  51. # A class variable so we can access it when we don't have access to the
  52. # instance that's being set.
  53. desc = ''
  54. def __init__(self, gerrit_change=None, **kwargs):
  55. self._gerrit_change = gerrit_change
  56. def GetIssue(self):
  57. return 1
  58. def FetchDescription(self):
  59. return ChangelistMock.desc
  60. def UpdateDescription(self, desc, force=False):
  61. ChangelistMock.desc = desc
  62. def GetGerritChange(self, patchset=None, **kwargs):
  63. del patchset
  64. return self._gerrit_change
  65. class GitMocks(object):
  66. def __init__(self, config=None, branchref=None):
  67. self.branchref = branchref or 'refs/heads/master'
  68. self.config = config or {}
  69. def GetBranchRef(self, _root):
  70. return self.branchref
  71. def NewBranch(self, branchref):
  72. self.branchref = branchref
  73. def GetConfig(self, root, key, default=None):
  74. if root != '':
  75. key = '%s:%s' % (root, key)
  76. return self.config.get(key, default)
  77. def SetConfig(self, root, key, value=None):
  78. if root != '':
  79. key = '%s:%s' % (root, key)
  80. if value:
  81. self.config[key] = value
  82. return
  83. if key not in self.config:
  84. raise CERR1
  85. del self.config[key]
  86. class WatchlistsMock(object):
  87. def __init__(self, _):
  88. pass
  89. @staticmethod
  90. def GetWatchersForPaths(_):
  91. return ['joe@example.com']
  92. class CodereviewSettingsFileMock(object):
  93. def __init__(self):
  94. pass
  95. # pylint: disable=no-self-use
  96. def read(self):
  97. return ('CODE_REVIEW_SERVER: gerrit.chromium.org\n' +
  98. 'GERRIT_HOST: True\n')
  99. class AuthenticatorMock(object):
  100. def __init__(self, *_args):
  101. pass
  102. def has_cached_credentials(self):
  103. return True
  104. def authorize(self, http):
  105. return http
  106. def CookiesAuthenticatorMockFactory(hosts_with_creds=None, same_auth=False):
  107. """Use to mock Gerrit/Git credentials from ~/.netrc or ~/.gitcookies.
  108. Usage:
  109. >>> self.mock(git_cl.gerrit_util, "CookiesAuthenticator",
  110. CookiesAuthenticatorMockFactory({'host': ('user', _, 'pass')})
  111. OR
  112. >>> self.mock(git_cl.gerrit_util, "CookiesAuthenticator",
  113. CookiesAuthenticatorMockFactory(
  114. same_auth=('user', '', 'pass'))
  115. """
  116. class CookiesAuthenticatorMock(git_cl.gerrit_util.CookiesAuthenticator):
  117. def __init__(self): # pylint: disable=super-init-not-called
  118. # Intentionally not calling super() because it reads actual cookie files.
  119. pass
  120. @classmethod
  121. def get_gitcookies_path(cls):
  122. return '~/.gitcookies'
  123. @classmethod
  124. def get_netrc_path(cls):
  125. return '~/.netrc'
  126. def _get_auth_for_host(self, host):
  127. if same_auth:
  128. return same_auth
  129. return (hosts_with_creds or {}).get(host)
  130. return CookiesAuthenticatorMock
  131. class MockChangelistWithBranchAndIssue():
  132. def __init__(self, branch, issue):
  133. self.branch = branch
  134. self.issue = issue
  135. def GetBranch(self):
  136. return self.branch
  137. def GetIssue(self):
  138. return self.issue
  139. class SystemExitMock(Exception):
  140. pass
  141. class TestGitClBasic(unittest.TestCase):
  142. def setUp(self):
  143. mock.patch('sys.exit', side_effect=SystemExitMock).start()
  144. mock.patch('sys.stdout', StringIO()).start()
  145. mock.patch('sys.stderr', StringIO()).start()
  146. self.addCleanup(mock.patch.stopall)
  147. def test_die_with_error(self):
  148. with self.assertRaises(SystemExitMock):
  149. git_cl.DieWithError('foo', git_cl.ChangeDescription('lorem ipsum'))
  150. self.assertEqual(sys.stderr.getvalue(), 'foo\n')
  151. self.assertTrue('saving CL description' in sys.stdout.getvalue())
  152. self.assertTrue('Content of CL description' in sys.stdout.getvalue())
  153. self.assertTrue('lorem ipsum' in sys.stdout.getvalue())
  154. sys.exit.assert_called_once_with(1)
  155. def test_die_with_error_no_desc(self):
  156. with self.assertRaises(SystemExitMock):
  157. git_cl.DieWithError('foo')
  158. self.assertEqual(sys.stderr.getvalue(), 'foo\n')
  159. self.assertEqual(sys.stdout.getvalue(), '')
  160. sys.exit.assert_called_once_with(1)
  161. def test_fetch_description(self):
  162. cl = git_cl.Changelist(issue=1, codereview_host='host')
  163. cl.description = 'x'
  164. self.assertEqual(cl.FetchDescription(), 'x')
  165. @mock.patch('git_cl.Changelist.EnsureAuthenticated')
  166. @mock.patch('git_cl.Changelist.GetStatus', lambda cl: cl.status)
  167. def test_get_cl_statuses(self, *_mocks):
  168. statuses = [
  169. 'closed', 'commit', 'dry-run', 'lgtm', 'reply', 'unsent', 'waiting']
  170. changes = []
  171. for status in statuses:
  172. cl = git_cl.Changelist()
  173. cl.status = status
  174. changes.append(cl)
  175. actual = set(git_cl.get_cl_statuses(changes, True))
  176. self.assertEqual(set(zip(changes, statuses)), actual)
  177. def test_get_cl_statuses_no_changes(self):
  178. self.assertEqual([], list(git_cl.get_cl_statuses([], True)))
  179. @mock.patch('git_cl.Changelist.EnsureAuthenticated')
  180. @mock.patch('multiprocessing.pool.ThreadPool')
  181. def test_get_cl_statuses_timeout(self, *_mocks):
  182. changes = [git_cl.Changelist() for _ in range(2)]
  183. pool = multiprocessing.pool.ThreadPool()
  184. it = pool.imap_unordered.return_value.__iter__ = mock.Mock()
  185. it.return_value.next.side_effect = [
  186. (changes[0], 'lgtm'),
  187. multiprocessing.TimeoutError,
  188. ]
  189. actual = list(git_cl.get_cl_statuses(changes, True))
  190. self.assertEqual([(changes[0], 'lgtm'), (changes[1], 'error')], actual)
  191. @mock.patch('git_cl.Changelist.GetIssueURL')
  192. def test_get_cl_statuses_not_finegrained(self, _mock):
  193. changes = [git_cl.Changelist() for _ in range(2)]
  194. urls = ['some-url', None]
  195. git_cl.Changelist.GetIssueURL.side_effect = urls
  196. actual = set(git_cl.get_cl_statuses(changes, False))
  197. self.assertEqual(
  198. set([(changes[0], 'waiting'), (changes[1], 'error')]), actual)
  199. def test_get_issue_url(self):
  200. cl = git_cl.Changelist(issue=123)
  201. cl._gerrit_server = 'https://example.com'
  202. self.assertEqual(cl.GetIssueURL(), 'https://example.com/123')
  203. self.assertEqual(cl.GetIssueURL(short=True), 'https://example.com/123')
  204. cl = git_cl.Changelist(issue=123)
  205. cl._gerrit_server = 'https://chromium-review.googlesource.com'
  206. self.assertEqual(cl.GetIssueURL(),
  207. 'https://chromium-review.googlesource.com/123')
  208. self.assertEqual(cl.GetIssueURL(short=True), 'https://crrev.com/c/123')
  209. def test_set_preserve_tryjobs(self):
  210. d = git_cl.ChangeDescription('Simple.')
  211. d.set_preserve_tryjobs()
  212. self.assertEqual(d.description.splitlines(), [
  213. 'Simple.',
  214. '',
  215. 'Cq-Do-Not-Cancel-Tryjobs: true',
  216. ])
  217. before = d.description
  218. d.set_preserve_tryjobs()
  219. self.assertEqual(before, d.description)
  220. d = git_cl.ChangeDescription('\n'.join([
  221. 'One is enough',
  222. '',
  223. 'Cq-Do-Not-Cancel-Tryjobs: dups not encouraged, but don\'t hurt',
  224. 'Change-Id: Ideadbeef',
  225. ]))
  226. d.set_preserve_tryjobs()
  227. self.assertEqual(d.description.splitlines(), [
  228. 'One is enough',
  229. '',
  230. 'Cq-Do-Not-Cancel-Tryjobs: dups not encouraged, but don\'t hurt',
  231. 'Change-Id: Ideadbeef',
  232. 'Cq-Do-Not-Cancel-Tryjobs: true',
  233. ])
  234. def test_get_bug_line_values(self):
  235. f = lambda p, bugs: list(git_cl._get_bug_line_values(p, bugs))
  236. self.assertEqual(f('', ''), [])
  237. self.assertEqual(f('', '123,v8:456'), ['123', 'v8:456'])
  238. self.assertEqual(f('v8', '456'), ['v8:456'])
  239. self.assertEqual(f('v8', 'chromium:123,456'), ['v8:456', 'chromium:123'])
  240. # Not nice, but not worth carying.
  241. self.assertEqual(f('v8', 'chromium:123,456,v8:123'),
  242. ['v8:456', 'chromium:123', 'v8:123'])
  243. def _test_git_number(self, parent_msg, dest_ref, child_msg,
  244. parent_hash='parenthash'):
  245. desc = git_cl.ChangeDescription(child_msg)
  246. desc.update_with_git_number_footers(parent_hash, parent_msg, dest_ref)
  247. return desc.description
  248. def assertEqualByLine(self, actual, expected):
  249. self.assertEqual(actual.splitlines(), expected.splitlines())
  250. def test_git_number_bad_parent(self):
  251. with self.assertRaises(ValueError):
  252. self._test_git_number('Parent', 'refs/heads/master', 'Child')
  253. def test_git_number_bad_parent_footer(self):
  254. with self.assertRaises(AssertionError):
  255. self._test_git_number(
  256. 'Parent\n'
  257. '\n'
  258. 'Cr-Commit-Position: wrong',
  259. 'refs/heads/master', 'Child')
  260. def test_git_number_bad_lineage_ignored(self):
  261. actual = self._test_git_number(
  262. 'Parent\n'
  263. '\n'
  264. 'Cr-Commit-Position: refs/heads/master@{#1}\n'
  265. 'Cr-Branched-From: mustBeReal40CharHash-branch@{#pos}',
  266. 'refs/heads/master', 'Child')
  267. self.assertEqualByLine(
  268. actual,
  269. 'Child\n'
  270. '\n'
  271. 'Cr-Commit-Position: refs/heads/master@{#2}\n'
  272. 'Cr-Branched-From: mustBeReal40CharHash-branch@{#pos}')
  273. def test_git_number_same_branch(self):
  274. actual = self._test_git_number(
  275. 'Parent\n'
  276. '\n'
  277. 'Cr-Commit-Position: refs/heads/master@{#12}',
  278. dest_ref='refs/heads/master',
  279. child_msg='Child')
  280. self.assertEqualByLine(
  281. actual,
  282. 'Child\n'
  283. '\n'
  284. 'Cr-Commit-Position: refs/heads/master@{#13}')
  285. def test_git_number_same_branch_mixed_footers(self):
  286. actual = self._test_git_number(
  287. 'Parent\n'
  288. '\n'
  289. 'Cr-Commit-Position: refs/heads/master@{#12}',
  290. dest_ref='refs/heads/master',
  291. child_msg='Child\n'
  292. '\n'
  293. 'Broken-by: design\n'
  294. 'BUG=123')
  295. self.assertEqualByLine(
  296. actual,
  297. 'Child\n'
  298. '\n'
  299. 'Broken-by: design\n'
  300. 'BUG=123\n'
  301. 'Cr-Commit-Position: refs/heads/master@{#13}')
  302. def test_git_number_same_branch_with_originals(self):
  303. actual = self._test_git_number(
  304. 'Parent\n'
  305. '\n'
  306. 'Cr-Commit-Position: refs/heads/master@{#12}',
  307. dest_ref='refs/heads/master',
  308. child_msg='Child\n'
  309. '\n'
  310. 'Some users are smart and insert their own footers\n'
  311. '\n'
  312. 'Cr-Whatever: value\n'
  313. 'Cr-Commit-Position: refs/copy/paste@{#22}')
  314. self.assertEqualByLine(
  315. actual,
  316. 'Child\n'
  317. '\n'
  318. 'Some users are smart and insert their own footers\n'
  319. '\n'
  320. 'Cr-Original-Whatever: value\n'
  321. 'Cr-Original-Commit-Position: refs/copy/paste@{#22}\n'
  322. 'Cr-Commit-Position: refs/heads/master@{#13}')
  323. def test_git_number_new_branch(self):
  324. actual = self._test_git_number(
  325. 'Parent\n'
  326. '\n'
  327. 'Cr-Commit-Position: refs/heads/master@{#12}',
  328. dest_ref='refs/heads/branch',
  329. child_msg='Child')
  330. self.assertEqualByLine(
  331. actual,
  332. 'Child\n'
  333. '\n'
  334. 'Cr-Commit-Position: refs/heads/branch@{#1}\n'
  335. 'Cr-Branched-From: parenthash-refs/heads/master@{#12}')
  336. def test_git_number_lineage(self):
  337. actual = self._test_git_number(
  338. 'Parent\n'
  339. '\n'
  340. 'Cr-Commit-Position: refs/heads/branch@{#1}\n'
  341. 'Cr-Branched-From: somehash-refs/heads/master@{#12}',
  342. dest_ref='refs/heads/branch',
  343. child_msg='Child')
  344. self.assertEqualByLine(
  345. actual,
  346. 'Child\n'
  347. '\n'
  348. 'Cr-Commit-Position: refs/heads/branch@{#2}\n'
  349. 'Cr-Branched-From: somehash-refs/heads/master@{#12}')
  350. def test_git_number_moooooooore_lineage(self):
  351. actual = self._test_git_number(
  352. 'Parent\n'
  353. '\n'
  354. 'Cr-Commit-Position: refs/heads/branch@{#5}\n'
  355. 'Cr-Branched-From: somehash-refs/heads/master@{#12}',
  356. dest_ref='refs/heads/mooore',
  357. child_msg='Child')
  358. self.assertEqualByLine(
  359. actual,
  360. 'Child\n'
  361. '\n'
  362. 'Cr-Commit-Position: refs/heads/mooore@{#1}\n'
  363. 'Cr-Branched-From: parenthash-refs/heads/branch@{#5}\n'
  364. 'Cr-Branched-From: somehash-refs/heads/master@{#12}')
  365. def test_git_number_ever_moooooooore_lineage(self):
  366. self.maxDiff = 10000 # pylint: disable=attribute-defined-outside-init
  367. actual = self._test_git_number(
  368. 'CQ commit on fresh new branch + numbering.\n'
  369. '\n'
  370. 'NOTRY=True\n'
  371. 'NOPRESUBMIT=True\n'
  372. 'BUG=\n'
  373. '\n'
  374. 'Review-Url: https://codereview.chromium.org/2577703003\n'
  375. 'Cr-Commit-Position: refs/heads/gnumb-test/br@{#1}\n'
  376. 'Cr-Branched-From: 0749ff9edc-refs/heads/gnumb-test/cq@{#4}\n'
  377. 'Cr-Branched-From: 5c49df2da6-refs/heads/master@{#41618}',
  378. dest_ref='refs/heads/gnumb-test/cl',
  379. child_msg='git cl on fresh new branch + numbering.\n'
  380. '\n'
  381. 'Review-Url: https://codereview.chromium.org/2575043003 .\n')
  382. self.assertEqualByLine(
  383. actual,
  384. 'git cl on fresh new branch + numbering.\n'
  385. '\n'
  386. 'Review-Url: https://codereview.chromium.org/2575043003 .\n'
  387. 'Cr-Commit-Position: refs/heads/gnumb-test/cl@{#1}\n'
  388. 'Cr-Branched-From: parenthash-refs/heads/gnumb-test/br@{#1}\n'
  389. 'Cr-Branched-From: 0749ff9edc-refs/heads/gnumb-test/cq@{#4}\n'
  390. 'Cr-Branched-From: 5c49df2da6-refs/heads/master@{#41618}')
  391. def test_git_number_cherry_pick(self):
  392. actual = self._test_git_number(
  393. 'Parent\n'
  394. '\n'
  395. 'Cr-Commit-Position: refs/heads/branch@{#1}\n'
  396. 'Cr-Branched-From: somehash-refs/heads/master@{#12}',
  397. dest_ref='refs/heads/branch',
  398. child_msg='Child, which is cherry-pick from master\n'
  399. '\n'
  400. 'Cr-Commit-Position: refs/heads/master@{#100}\n'
  401. '(cherry picked from commit deadbeef12345678deadbeef12345678deadbeef)')
  402. self.assertEqualByLine(
  403. actual,
  404. 'Child, which is cherry-pick from master\n'
  405. '\n'
  406. '(cherry picked from commit deadbeef12345678deadbeef12345678deadbeef)\n'
  407. '\n'
  408. 'Cr-Original-Commit-Position: refs/heads/master@{#100}\n'
  409. 'Cr-Commit-Position: refs/heads/branch@{#2}\n'
  410. 'Cr-Branched-From: somehash-refs/heads/master@{#12}')
  411. @mock.patch('gerrit_util.GetAccountDetails')
  412. def test_valid_accounts(self, mockGetAccountDetails):
  413. mock_per_account = {
  414. 'u1': None, # 404, doesn't exist.
  415. 'u2': {
  416. '_account_id': 123124,
  417. 'avatars': [],
  418. 'email': 'u2@example.com',
  419. 'name': 'User Number 2',
  420. 'status': 'OOO',
  421. },
  422. 'u3': git_cl.gerrit_util.GerritError(500, 'retries didn\'t help :('),
  423. }
  424. def GetAccountDetailsMock(_, account):
  425. # Poor-man's mock library's side_effect.
  426. v = mock_per_account.pop(account)
  427. if isinstance(v, Exception):
  428. raise v
  429. return v
  430. mockGetAccountDetails.side_effect = GetAccountDetailsMock
  431. actual = git_cl.gerrit_util.ValidAccounts(
  432. 'host', ['u1', 'u2', 'u3'], max_threads=1)
  433. self.assertEqual(actual, {
  434. 'u2': {
  435. '_account_id': 123124,
  436. 'avatars': [],
  437. 'email': 'u2@example.com',
  438. 'name': 'User Number 2',
  439. 'status': 'OOO',
  440. },
  441. })
  442. class TestParseIssueURL(unittest.TestCase):
  443. def _validate(self, parsed, issue=None, patchset=None, hostname=None,
  444. fail=False):
  445. self.assertIsNotNone(parsed)
  446. if fail:
  447. self.assertFalse(parsed.valid)
  448. return
  449. self.assertTrue(parsed.valid)
  450. self.assertEqual(parsed.issue, issue)
  451. self.assertEqual(parsed.patchset, patchset)
  452. self.assertEqual(parsed.hostname, hostname)
  453. def test_ParseIssueNumberArgument(self):
  454. def test(arg, *args, **kwargs):
  455. self._validate(git_cl.ParseIssueNumberArgument(arg), *args, **kwargs)
  456. test('123', 123)
  457. test('', fail=True)
  458. test('abc', fail=True)
  459. test('123/1', fail=True)
  460. test('123a', fail=True)
  461. test('ssh://chrome-review.source.com/#/c/123/4/', fail=True)
  462. test('https://codereview.source.com/123',
  463. 123, None, 'codereview.source.com')
  464. test('http://chrome-review.source.com/c/123',
  465. 123, None, 'chrome-review.source.com')
  466. test('https://chrome-review.source.com/c/123/',
  467. 123, None, 'chrome-review.source.com')
  468. test('https://chrome-review.source.com/c/123/4',
  469. 123, 4, 'chrome-review.source.com')
  470. test('https://chrome-review.source.com/#/c/123/4',
  471. 123, 4, 'chrome-review.source.com')
  472. test('https://chrome-review.source.com/c/123/4',
  473. 123, 4, 'chrome-review.source.com')
  474. test('https://chrome-review.source.com/123',
  475. 123, None, 'chrome-review.source.com')
  476. test('https://chrome-review.source.com/123/4',
  477. 123, 4, 'chrome-review.source.com')
  478. test('https://chrome-review.source.com/bad/123/4', fail=True)
  479. test('https://chrome-review.source.com/c/123/1/whatisthis', fail=True)
  480. test('https://chrome-review.source.com/c/abc/', fail=True)
  481. test('ssh://chrome-review.source.com/c/123/1/', fail=True)
  482. class GitCookiesCheckerTest(unittest.TestCase):
  483. def setUp(self):
  484. super(GitCookiesCheckerTest, self).setUp()
  485. self.c = git_cl._GitCookiesChecker()
  486. self.c._all_hosts = []
  487. mock.patch('sys.stdout', StringIO()).start()
  488. self.addCleanup(mock.patch.stopall)
  489. def mock_hosts_creds(self, subhost_identity_pairs):
  490. def ensure_googlesource(h):
  491. if not h.endswith(self.c._GOOGLESOURCE):
  492. assert not h.endswith('.')
  493. return h + '.' + self.c._GOOGLESOURCE
  494. return h
  495. self.c._all_hosts = [(ensure_googlesource(h), i, '.gitcookies')
  496. for h, i in subhost_identity_pairs]
  497. def test_identity_parsing(self):
  498. self.assertEqual(self.c._parse_identity('ldap.google.com'),
  499. ('ldap', 'google.com'))
  500. self.assertEqual(self.c._parse_identity('git-ldap.example.com'),
  501. ('ldap', 'example.com'))
  502. # Specical case because we know there are no subdomains in chromium.org.
  503. self.assertEqual(self.c._parse_identity('git-note.period.chromium.org'),
  504. ('note.period', 'chromium.org'))
  505. # Pathological: ".period." can be either username OR domain, more likely
  506. # domain.
  507. self.assertEqual(self.c._parse_identity('git-note.period.example.com'),
  508. ('note', 'period.example.com'))
  509. def test_analysis_nothing(self):
  510. self.c._all_hosts = []
  511. self.assertFalse(self.c.has_generic_host())
  512. self.assertEqual(set(), self.c.get_conflicting_hosts())
  513. self.assertEqual(set(), self.c.get_duplicated_hosts())
  514. self.assertEqual(set(), self.c.get_partially_configured_hosts())
  515. self.assertEqual(set(), self.c.get_hosts_with_wrong_identities())
  516. def test_analysis(self):
  517. self.mock_hosts_creds([
  518. ('.googlesource.com', 'git-example.chromium.org'),
  519. ('chromium', 'git-example.google.com'),
  520. ('chromium-review', 'git-example.google.com'),
  521. ('chrome-internal', 'git-example.chromium.org'),
  522. ('chrome-internal-review', 'git-example.chromium.org'),
  523. ('conflict', 'git-example.google.com'),
  524. ('conflict-review', 'git-example.chromium.org'),
  525. ('dup', 'git-example.google.com'),
  526. ('dup', 'git-example.google.com'),
  527. ('dup-review', 'git-example.google.com'),
  528. ('partial', 'git-example.google.com'),
  529. ('gpartial-review', 'git-example.google.com'),
  530. ])
  531. self.assertTrue(self.c.has_generic_host())
  532. self.assertEqual(set(['conflict.googlesource.com']),
  533. self.c.get_conflicting_hosts())
  534. self.assertEqual(set(['dup.googlesource.com']),
  535. self.c.get_duplicated_hosts())
  536. self.assertEqual(set(['partial.googlesource.com',
  537. 'gpartial-review.googlesource.com']),
  538. self.c.get_partially_configured_hosts())
  539. self.assertEqual(set(['chromium.googlesource.com',
  540. 'chrome-internal.googlesource.com']),
  541. self.c.get_hosts_with_wrong_identities())
  542. def test_report_no_problems(self):
  543. self.test_analysis_nothing()
  544. self.assertFalse(self.c.find_and_report_problems())
  545. self.assertEqual(sys.stdout.getvalue(), '')
  546. @mock.patch(
  547. 'git_cl.gerrit_util.CookiesAuthenticator.get_gitcookies_path',
  548. return_value='~/.gitcookies')
  549. def test_report(self, *_mocks):
  550. self.test_analysis()
  551. self.assertTrue(self.c.find_and_report_problems())
  552. with open(os.path.join(os.path.dirname(__file__),
  553. 'git_cl_creds_check_report.txt')) as f:
  554. expected = f.read()
  555. def by_line(text):
  556. return [l.rstrip() for l in text.rstrip().splitlines()]
  557. self.maxDiff = 10000 # pylint: disable=attribute-defined-outside-init
  558. self.assertEqual(by_line(sys.stdout.getvalue().strip()), by_line(expected))
  559. class TestGitCl(unittest.TestCase):
  560. def setUp(self):
  561. super(TestGitCl, self).setUp()
  562. self.calls = []
  563. self._calls_done = []
  564. mock.patch('sys.stdout', StringIO()).start()
  565. mock.patch(
  566. 'git_cl.time_time',
  567. lambda: self._mocked_call('time.time')).start()
  568. mock.patch(
  569. 'git_cl.metrics.collector.add_repeated',
  570. lambda *a: self._mocked_call('add_repeated', *a)).start()
  571. mock.patch('subprocess2.call', self._mocked_call).start()
  572. mock.patch('subprocess2.check_call', self._mocked_call).start()
  573. mock.patch('subprocess2.check_output', self._mocked_call).start()
  574. mock.patch(
  575. 'subprocess2.communicate',
  576. lambda *a, **_k: ([self._mocked_call(*a), ''], 0)).start()
  577. mock.patch(
  578. 'git_cl.gclient_utils.CheckCallAndFilter',
  579. self._mocked_call).start()
  580. mock.patch('git_common.is_dirty_git_tree', lambda x: False).start()
  581. mock.patch(
  582. 'git_common.get_or_create_merge_base',
  583. lambda *a: self._mocked_call('get_or_create_merge_base', *a)).start()
  584. mock.patch('git_cl.BranchExists', return_value=True).start()
  585. mock.patch('git_cl.FindCodereviewSettingsFile', return_value='').start()
  586. mock.patch(
  587. 'git_cl.SaveDescriptionBackup',
  588. lambda _: self._mocked_call('SaveDescriptionBackup')).start()
  589. mock.patch(
  590. 'git_cl.write_json',
  591. lambda *a: self._mocked_call('write_json', *a)).start()
  592. mock.patch(
  593. 'git_cl.Changelist.RunHook',
  594. return_value={'more_cc': ['test-more-cc@chromium.org']}).start()
  595. mock.patch('git_cl.watchlists.Watchlists', WatchlistsMock).start()
  596. mock.patch('git_cl.auth.Authenticator', AuthenticatorMock).start()
  597. mock.patch('gerrit_util.GetChangeDetail').start()
  598. mock.patch(
  599. 'git_cl.gerrit_util.GetChangeComments',
  600. lambda *a: self._mocked_call('GetChangeComments', *a)).start()
  601. mock.patch(
  602. 'git_cl.gerrit_util.GetChangeRobotComments',
  603. lambda *a: self._mocked_call('GetChangeRobotComments', *a)).start()
  604. mock.patch(
  605. 'git_cl.gerrit_util.AddReviewers',
  606. lambda *a: self._mocked_call('AddReviewers', *a)).start()
  607. mock.patch(
  608. 'git_cl.gerrit_util.SetReview',
  609. lambda h, i, msg=None, labels=None, notify=None, ready=None: (
  610. self._mocked_call(
  611. 'SetReview', h, i, msg, labels, notify, ready))).start()
  612. mock.patch(
  613. 'git_cl.gerrit_util.LuciContextAuthenticator.is_luci',
  614. return_value=False).start()
  615. mock.patch(
  616. 'git_cl.gerrit_util.GceAuthenticator.is_gce',
  617. return_value=False).start()
  618. mock.patch(
  619. 'git_cl.gerrit_util.ValidAccounts',
  620. lambda *a: self._mocked_call('ValidAccounts', *a)).start()
  621. mock.patch('sys.exit', side_effect=SystemExitMock).start()
  622. mock.patch('git_cl.Settings.GetRoot', return_value='').start()
  623. self.mockGit = GitMocks()
  624. mock.patch('scm.GIT.GetBranchRef', self.mockGit.GetBranchRef).start()
  625. mock.patch('scm.GIT.GetConfig', self.mockGit.GetConfig).start()
  626. mock.patch('scm.GIT.SetConfig', self.mockGit.SetConfig).start()
  627. mock.patch(
  628. 'git_new_branch.create_new_branch', self.mockGit.NewBranch).start()
  629. mock.patch(
  630. 'scm.GIT.FetchUpstreamTuple',
  631. return_value=('origin', 'refs/heads/master')).start()
  632. mock.patch(
  633. 'scm.GIT.CaptureStatus', return_value=[('M', 'foo.txt')]).start()
  634. # It's important to reset settings to not have inter-tests interference.
  635. git_cl.settings = None
  636. self.addCleanup(mock.patch.stopall)
  637. def tearDown(self):
  638. try:
  639. self.assertEqual([], self.calls)
  640. except AssertionError:
  641. calls = ''.join(' %s\n' % str(call) for call in self.calls[:5])
  642. if len(self.calls) > 5:
  643. calls += ' ...\n'
  644. self.fail(
  645. '\n'
  646. 'There are un-consumed calls after this test has finished:\n' +
  647. calls)
  648. finally:
  649. super(TestGitCl, self).tearDown()
  650. def _mocked_call(self, *args, **_kwargs):
  651. self.assertTrue(
  652. self.calls,
  653. '@%d Expected: <Missing> Actual: %r' % (len(self._calls_done), args))
  654. top = self.calls.pop(0)
  655. expected_args, result = top
  656. # Also logs otherwise it could get caught in a try/finally and be hard to
  657. # diagnose.
  658. if expected_args != args:
  659. N = 5
  660. prior_calls = '\n '.join(
  661. '@%d: %r' % (len(self._calls_done) - N + i, c[0])
  662. for i, c in enumerate(self._calls_done[-N:]))
  663. following_calls = '\n '.join(
  664. '@%d: %r' % (len(self._calls_done) + i + 1, c[0])
  665. for i, c in enumerate(self.calls[:N]))
  666. extended_msg = (
  667. 'A few prior calls:\n %s\n\n'
  668. 'This (expected):\n @%d: %r\n'
  669. 'This (actual):\n @%d: %r\n\n'
  670. 'A few following expected calls:\n %s' %
  671. (prior_calls, len(self._calls_done), expected_args,
  672. len(self._calls_done), args, following_calls))
  673. self.fail('@%d\n'
  674. ' Expected: %r\n'
  675. ' Actual: %r\n'
  676. '\n'
  677. '%s' % (
  678. len(self._calls_done), expected_args, args, extended_msg))
  679. self._calls_done.append(top)
  680. if isinstance(result, Exception):
  681. raise result
  682. # stdout from git commands is supposed to be a bytestream. Convert it here
  683. # instead of converting all test output in this file to bytes.
  684. if args[0][0] == 'git' and not isinstance(result, bytes):
  685. result = result.encode('utf-8')
  686. return result
  687. @mock.patch('sys.stdin', StringIO('blah\nye\n'))
  688. @mock.patch('sys.stdout', StringIO())
  689. def test_ask_for_explicit_yes_true(self):
  690. self.assertTrue(git_cl.ask_for_explicit_yes('prompt'))
  691. self.assertEqual(
  692. 'prompt [Yes/No]: Please, type yes or no: ',
  693. sys.stdout.getvalue())
  694. def test_LoadCodereviewSettingsFromFile_gerrit(self):
  695. codereview_file = StringIO('GERRIT_HOST: true')
  696. self.calls = [
  697. ((['git', 'config', '--unset-all', 'rietveld.cc'],), CERR1),
  698. ((['git', 'config', '--unset-all', 'rietveld.tree-status-url'],), CERR1),
  699. ((['git', 'config', '--unset-all', 'rietveld.viewvc-url'],), CERR1),
  700. ((['git', 'config', '--unset-all', 'rietveld.bug-prefix'],), CERR1),
  701. ((['git', 'config', '--unset-all', 'rietveld.cpplint-regex'],), CERR1),
  702. ((['git', 'config', '--unset-all', 'rietveld.cpplint-ignore-regex'],),
  703. CERR1),
  704. ((['git', 'config', '--unset-all', 'rietveld.run-post-upload-hook'],),
  705. CERR1),
  706. ((['git', 'config', '--unset-all', 'rietveld.format-full-by-default'],),
  707. CERR1),
  708. ((['git', 'config', 'gerrit.host', 'true'],), ''),
  709. ]
  710. self.assertIsNone(git_cl.LoadCodereviewSettingsFromFile(codereview_file))
  711. @classmethod
  712. def _gerrit_base_calls(cls, issue=None, fetched_description=None,
  713. fetched_status=None, other_cl_owner=None,
  714. custom_cl_base=None, short_hostname='chromium',
  715. change_id=None):
  716. calls = []
  717. if custom_cl_base:
  718. ancestor_revision = custom_cl_base
  719. else:
  720. # Determine ancestor_revision to be merge base.
  721. ancestor_revision = 'fake_ancestor_sha'
  722. calls += [
  723. (('get_or_create_merge_base', 'master', 'refs/remotes/origin/master'),
  724. ancestor_revision),
  725. ]
  726. if issue:
  727. gerrit_util.GetChangeDetail.return_value = {
  728. 'owner': {'email': (other_cl_owner or 'owner@example.com')},
  729. 'change_id': (change_id or '123456789'),
  730. 'current_revision': 'sha1_of_current_revision',
  731. 'revisions': {'sha1_of_current_revision': {
  732. 'commit': {'message': fetched_description},
  733. }},
  734. 'status': fetched_status or 'NEW',
  735. }
  736. if fetched_status == 'ABANDONED':
  737. return calls
  738. if other_cl_owner:
  739. calls += [
  740. (('ask_for_data', 'Press Enter to upload, or Ctrl+C to abort'), ''),
  741. ]
  742. calls += [
  743. ((['git', 'diff', '--no-ext-diff', '--stat', '-l100000', '-C50'] +
  744. ([custom_cl_base] if custom_cl_base else
  745. [ancestor_revision, 'HEAD']),),
  746. '+dat'),
  747. ]
  748. return calls
  749. def _gerrit_upload_calls(self, description, reviewers, squash,
  750. squash_mode='default',
  751. expected_upstream_ref='origin/refs/heads/master',
  752. title=None, notify=False,
  753. post_amend_description=None, issue=None, cc=None,
  754. custom_cl_base=None, tbr=None,
  755. short_hostname='chromium',
  756. labels=None, change_id=None, original_title=None,
  757. final_description=None, gitcookies_exists=True,
  758. force=False, edit_description=None):
  759. if post_amend_description is None:
  760. post_amend_description = description
  761. cc = cc or []
  762. calls = []
  763. if squash_mode in ('override_squash', 'override_nosquash'):
  764. self.mockGit.config['gerrit.override-squash-uploads'] = (
  765. 'true' if squash_mode == 'override_squash' else 'false')
  766. # If issue is given, then description is fetched from Gerrit instead.
  767. if issue is None:
  768. if squash:
  769. title = 'Initial_upload'
  770. else:
  771. if not title:
  772. calls += [
  773. ((['git', 'show', '-s', '--format=%s', 'HEAD'],), ''),
  774. (('ask_for_data', 'Title for patchset []: '), 'User input'),
  775. ]
  776. title = 'User_input'
  777. if not git_footers.get_footer_change_id(description) and not squash:
  778. calls += [
  779. (('DownloadGerritHook', False), ''),
  780. ]
  781. if squash:
  782. if not force and not issue:
  783. calls += [
  784. ((['RunEditor'],), description),
  785. ]
  786. # user wants to edit description
  787. if edit_description:
  788. calls += [
  789. ((['RunEditor'],), edit_description),
  790. ]
  791. ref_to_push = 'abcdef0123456789'
  792. if custom_cl_base is None:
  793. calls += [
  794. (('get_or_create_merge_base', 'master', 'refs/remotes/origin/master'),
  795. 'origin/master'),
  796. ]
  797. parent = 'origin/master'
  798. else:
  799. calls += [
  800. ((['git', 'merge-base', '--is-ancestor', custom_cl_base,
  801. 'refs/remotes/origin/master'],),
  802. callError(1)), # Means not ancenstor.
  803. (('ask_for_data',
  804. 'Do you take responsibility for cleaning up potential mess '
  805. 'resulting from proceeding with upload? Press Enter to upload, '
  806. 'or Ctrl+C to abort'), ''),
  807. ]
  808. parent = custom_cl_base
  809. calls += [
  810. ((['git', 'rev-parse', 'HEAD:'],), # `HEAD:` means HEAD's tree hash.
  811. '0123456789abcdef'),
  812. ((['FileWrite', '/tmp/fake-temp1', description],), None),
  813. ((['git', 'commit-tree', '0123456789abcdef', '-p', parent,
  814. '-F', '/tmp/fake-temp1'],),
  815. ref_to_push),
  816. ]
  817. else:
  818. ref_to_push = 'HEAD'
  819. calls += [
  820. (('SaveDescriptionBackup',), None),
  821. ((['git', 'rev-list',
  822. (custom_cl_base if custom_cl_base else expected_upstream_ref) + '..' +
  823. ref_to_push],),
  824. '1hashPerLine\n'),
  825. ]
  826. metrics_arguments = []
  827. if notify:
  828. ref_suffix = '%ready,notify=ALL'
  829. metrics_arguments += ['ready', 'notify=ALL']
  830. else:
  831. if not issue and squash:
  832. ref_suffix = '%wip'
  833. metrics_arguments.append('wip')
  834. else:
  835. ref_suffix = '%notify=NONE'
  836. metrics_arguments.append('notify=NONE')
  837. if title:
  838. ref_suffix += ',m=' + title
  839. metrics_arguments.append('m')
  840. if short_hostname == 'chromium':
  841. # All reviwers and ccs get into ref_suffix.
  842. for r in sorted(reviewers):
  843. ref_suffix += ',r=%s' % r
  844. metrics_arguments.append('r')
  845. if issue is None:
  846. cc += ['test-more-cc@chromium.org', 'joe@example.com']
  847. for c in sorted(cc):
  848. ref_suffix += ',cc=%s' % c
  849. metrics_arguments.append('cc')
  850. reviewers, cc = [], []
  851. else:
  852. # TODO(crbug/877717): remove this case.
  853. calls += [
  854. (('ValidAccounts', '%s-review.googlesource.com' % short_hostname,
  855. sorted(reviewers) + ['joe@example.com',
  856. 'test-more-cc@chromium.org'] + cc),
  857. {
  858. e: {'email': e}
  859. for e in (reviewers + ['joe@example.com'] + cc)
  860. })
  861. ]
  862. for r in sorted(reviewers):
  863. if r != 'bad-account-or-email':
  864. ref_suffix += ',r=%s' % r
  865. metrics_arguments.append('r')
  866. reviewers.remove(r)
  867. if issue is None:
  868. cc += ['joe@example.com']
  869. for c in sorted(cc):
  870. ref_suffix += ',cc=%s' % c
  871. metrics_arguments.append('cc')
  872. if c in cc:
  873. cc.remove(c)
  874. for k, v in sorted((labels or {}).items()):
  875. ref_suffix += ',l=%s+%d' % (k, v)
  876. metrics_arguments.append('l=%s+%d' % (k, v))
  877. if tbr:
  878. calls += [
  879. (('GetCodeReviewTbrScore',
  880. '%s-review.googlesource.com' % short_hostname,
  881. 'my/repo'),
  882. 2,),
  883. ]
  884. calls += [
  885. (('time.time',), 1000,),
  886. ((['git', 'push',
  887. 'https://%s.googlesource.com/my/repo' % short_hostname,
  888. ref_to_push + ':refs/for/refs/heads/master' + ref_suffix],),
  889. (('remote:\n'
  890. 'remote: Processing changes: (\)\n'
  891. 'remote: Processing changes: (|)\n'
  892. 'remote: Processing changes: (/)\n'
  893. 'remote: Processing changes: (-)\n'
  894. 'remote: Processing changes: new: 1 (/)\n'
  895. 'remote: Processing changes: new: 1, done\n'
  896. 'remote:\n'
  897. 'remote: New Changes:\n'
  898. 'remote: https://%s-review.googlesource.com/#/c/my/repo/+/123456'
  899. ' XXX\n'
  900. 'remote:\n'
  901. 'To https://%s.googlesource.com/my/repo\n'
  902. ' * [new branch] hhhh -> refs/for/refs/heads/master\n'
  903. ) % (short_hostname, short_hostname)),),
  904. (('time.time',), 2000,),
  905. (('add_repeated',
  906. 'sub_commands',
  907. {
  908. 'execution_time': 1000,
  909. 'command': 'git push',
  910. 'exit_code': 0,
  911. 'arguments': sorted(metrics_arguments),
  912. }),
  913. None,),
  914. ]
  915. final_description = final_description or post_amend_description.strip()
  916. original_title = original_title or title or '<untitled>'
  917. # Trace-related calls
  918. calls += [
  919. # Write a description with context for the current trace.
  920. ((['FileWrite', 'TRACES_DIR/20170316T200041.000000-README',
  921. 'Thu Mar 16 20:00:41 2017\n'
  922. '%(short_hostname)s-review.googlesource.com\n'
  923. '%(change_id)s\n'
  924. '%(title)s\n'
  925. '%(description)s\n'
  926. '1000\n'
  927. '0\n'
  928. '%(trace_name)s' % {
  929. 'short_hostname': short_hostname,
  930. 'change_id': change_id,
  931. 'description': final_description,
  932. 'title': original_title,
  933. 'trace_name': 'TRACES_DIR/20170316T200041.000000',
  934. }],),
  935. None,
  936. ),
  937. # Read traces and shorten git hashes.
  938. ((['os.path.isfile', 'TEMP_DIR/trace-packet'],),
  939. True,
  940. ),
  941. ((['FileRead', 'TEMP_DIR/trace-packet'],),
  942. ('git-hash: 0123456789012345678901234567890123456789\n'
  943. 'git-hash: abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde\n'),
  944. ),
  945. ((['FileWrite', 'TEMP_DIR/trace-packet',
  946. 'git-hash: 012345\n'
  947. 'git-hash: abcdea\n'],),
  948. None,
  949. ),
  950. # Make zip file for the git traces.
  951. ((['make_archive', 'TRACES_DIR/20170316T200041.000000-traces', 'zip',
  952. 'TEMP_DIR'],),
  953. None,
  954. ),
  955. # Collect git config and gitcookies.
  956. ((['git', 'config', '-l'],),
  957. 'git-config-output',
  958. ),
  959. ((['FileWrite', 'TEMP_DIR/git-config', 'git-config-output'],),
  960. None,
  961. ),
  962. ((['os.path.isfile', '~/.gitcookies'],),
  963. gitcookies_exists,
  964. ),
  965. ]
  966. if gitcookies_exists:
  967. calls += [
  968. ((['FileRead', '~/.gitcookies'],),
  969. 'gitcookies 1/SECRET',
  970. ),
  971. ((['FileWrite', 'TEMP_DIR/gitcookies', 'gitcookies REDACTED'],),
  972. None,
  973. ),
  974. ]
  975. calls += [
  976. # Make zip file for the git config and gitcookies.
  977. ((['make_archive', 'TRACES_DIR/20170316T200041.000000-git-info', 'zip',
  978. 'TEMP_DIR'],),
  979. None,
  980. ),
  981. ]
  982. # TODO(crbug/877717): this should never be used.
  983. if squash and short_hostname != 'chromium':
  984. calls += [
  985. (('AddReviewers',
  986. 'chromium-review.googlesource.com', 'my%2Frepo~123456',
  987. sorted(reviewers),
  988. cc + ['test-more-cc@chromium.org'],
  989. notify),
  990. ''),
  991. ]
  992. calls += [
  993. ((['git', 'rev-parse', 'HEAD'],), 'hash'),
  994. ]
  995. return calls
  996. def _run_gerrit_upload_test(
  997. self,
  998. upload_args,
  999. description,
  1000. reviewers=None,
  1001. squash=True,
  1002. squash_mode=None,
  1003. expected_upstream_ref='origin/refs/heads/master',
  1004. title=None,
  1005. notify=False,
  1006. post_amend_description=None,
  1007. issue=None,
  1008. cc=None,
  1009. fetched_status=None,
  1010. other_cl_owner=None,
  1011. custom_cl_base=None,
  1012. tbr=None,
  1013. short_hostname='chromium',
  1014. labels=None,
  1015. change_id=None,
  1016. original_title=None,
  1017. final_description=None,
  1018. gitcookies_exists=True,
  1019. force=False,
  1020. edit_description=None,
  1021. fetched_description=None):
  1022. """Generic gerrit upload test framework."""
  1023. if squash_mode is None:
  1024. if '--no-squash' in upload_args:
  1025. squash_mode = 'nosquash'
  1026. elif '--squash' in upload_args:
  1027. squash_mode = 'squash'
  1028. else:
  1029. squash_mode = 'default'
  1030. reviewers = reviewers or []
  1031. cc = cc or []
  1032. mock.patch('git_cl.gerrit_util.CookiesAuthenticator',
  1033. CookiesAuthenticatorMockFactory(
  1034. same_auth=('git-owner.example.com', '', 'pass'))).start()
  1035. mock.patch('git_cl.Changelist._GerritCommitMsgHookCheck',
  1036. lambda _, offer_removal: None).start()
  1037. mock.patch('git_cl.gclient_utils.RunEditor',
  1038. lambda *_, **__: self._mocked_call(['RunEditor'])).start()
  1039. mock.patch('git_cl.DownloadGerritHook', lambda force: self._mocked_call(
  1040. 'DownloadGerritHook', force)).start()
  1041. mock.patch('git_cl.gclient_utils.FileRead',
  1042. lambda path: self._mocked_call(['FileRead', path])).start()
  1043. mock.patch('git_cl.gclient_utils.FileWrite',
  1044. lambda path, contents: self._mocked_call(
  1045. ['FileWrite', path, contents])).start()
  1046. mock.patch('git_cl.datetime_now',
  1047. lambda: datetime.datetime(2017, 3, 16, 20, 0, 41, 0)).start()
  1048. mock.patch('git_cl.tempfile.mkdtemp', lambda: 'TEMP_DIR').start()
  1049. mock.patch('git_cl.TRACES_DIR', 'TRACES_DIR').start()
  1050. mock.patch('git_cl.TRACES_README_FORMAT',
  1051. '%(now)s\n'
  1052. '%(gerrit_host)s\n'
  1053. '%(change_id)s\n'
  1054. '%(title)s\n'
  1055. '%(description)s\n'
  1056. '%(execution_time)s\n'
  1057. '%(exit_code)s\n'
  1058. '%(trace_name)s').start()
  1059. mock.patch('git_cl.shutil.make_archive',
  1060. lambda *args: self._mocked_call(['make_archive'] +
  1061. list(args))).start()
  1062. mock.patch('os.path.isfile',
  1063. lambda path: self._mocked_call(['os.path.isfile', path])).start()
  1064. mock.patch('git_cl.Changelist.GitSanityChecks', return_value=True).start()
  1065. mock.patch(
  1066. 'git_cl._create_description_from_log', return_value=description).start()
  1067. mock.patch(
  1068. 'git_cl.Changelist._AddChangeIdToCommitMessage',
  1069. return_value=post_amend_description or description).start()
  1070. mock.patch(
  1071. 'git_cl.ask_for_data',
  1072. lambda prompt: self._mocked_call('ask_for_data', prompt)).start()
  1073. self.mockGit.config['gerrit.host'] = 'true'
  1074. self.mockGit.config['branch.master.gerritissue'] = (
  1075. str(issue) if issue else None)
  1076. self.mockGit.config['remote.origin.url'] = (
  1077. 'https://%s.googlesource.com/my/repo' % short_hostname)
  1078. self.mockGit.config['user.email'] = 'me@example.com'
  1079. self.calls = self._gerrit_base_calls(
  1080. issue=issue,
  1081. fetched_description=fetched_description or description,
  1082. fetched_status=fetched_status,
  1083. other_cl_owner=other_cl_owner,
  1084. custom_cl_base=custom_cl_base,
  1085. short_hostname=short_hostname,
  1086. change_id=change_id)
  1087. if fetched_status != 'ABANDONED':
  1088. mock.patch(
  1089. 'gclient_utils.temporary_file', TemporaryFileMock()).start()
  1090. mock.patch('os.remove', return_value=True).start()
  1091. self.calls += self._gerrit_upload_calls(
  1092. description, reviewers, squash,
  1093. squash_mode=squash_mode,
  1094. expected_upstream_ref=expected_upstream_ref,
  1095. title=title, notify=notify,
  1096. post_amend_description=post_amend_description,
  1097. issue=issue, cc=cc,
  1098. custom_cl_base=custom_cl_base, tbr=tbr,
  1099. short_hostname=short_hostname,
  1100. labels=labels,
  1101. change_id=change_id,
  1102. original_title=original_title,
  1103. final_description=final_description,
  1104. gitcookies_exists=gitcookies_exists,
  1105. force=force,
  1106. edit_description=edit_description)
  1107. # Uncomment when debugging.
  1108. # print('\n'.join(map(lambda x: '%2i: %s' % x, enumerate(self.calls))))
  1109. git_cl.main(['upload'] + upload_args)
  1110. if squash:
  1111. self.assertIssueAndPatchset(patchset=None)
  1112. self.assertEqual(
  1113. 'abcdef0123456789',
  1114. scm.GIT.GetBranchConfig('', 'master', 'gerritsquashhash'))
  1115. def test_gerrit_upload_traces_no_gitcookies(self):
  1116. self._run_gerrit_upload_test(
  1117. ['--no-squash'],
  1118. 'desc ✔\n\nBUG=\n',
  1119. [],
  1120. squash=False,
  1121. post_amend_description='desc ✔\n\nBUG=\n\nChange-Id: Ixxx',
  1122. change_id='Ixxx',
  1123. gitcookies_exists=False)
  1124. def test_gerrit_upload_without_change_id(self):
  1125. self._run_gerrit_upload_test(
  1126. ['--no-squash'],
  1127. 'desc ✔\n\nBUG=\n',
  1128. [],
  1129. squash=False,
  1130. post_amend_description='desc ✔\n\nBUG=\n\nChange-Id: Ixxx',
  1131. change_id='Ixxx')
  1132. def test_gerrit_upload_without_change_id_override_nosquash(self):
  1133. self._run_gerrit_upload_test(
  1134. [],
  1135. 'desc ✔\n\nBUG=\n',
  1136. [],
  1137. squash=False,
  1138. squash_mode='override_nosquash',
  1139. post_amend_description='desc ✔\n\nBUG=\n\nChange-Id: Ixxx',
  1140. change_id='Ixxx')
  1141. def test_gerrit_no_reviewer(self):
  1142. self._run_gerrit_upload_test(
  1143. [],
  1144. 'desc ✔\n\nBUG=\n\nChange-Id: I123456789\n',
  1145. [],
  1146. squash=False,
  1147. squash_mode='override_nosquash',
  1148. change_id='I123456789')
  1149. def test_gerrit_no_reviewer_non_chromium_host(self):
  1150. # TODO(crbug/877717): remove this test case.
  1151. self._run_gerrit_upload_test(
  1152. [],
  1153. 'desc ✔\n\nBUG=\n\nChange-Id: I123456789\n',
  1154. [],
  1155. squash=False,
  1156. squash_mode='override_nosquash',
  1157. short_hostname='other',
  1158. change_id='I123456789')
  1159. def test_gerrit_patchset_title_special_chars(self):
  1160. self._run_gerrit_upload_test(
  1161. ['-f', '-t', 'We\'ll escape ^_ ^ special chars...@{u}'],
  1162. 'desc ✔\n\nBUG=\n\nChange-Id: I123456789',
  1163. squash=False,
  1164. squash_mode='override_nosquash',
  1165. title='We%27ll_escape_%5E%5F_%5E_special_chars%2E%2E%2E%40%7Bu%7D',
  1166. change_id='I123456789',
  1167. original_title='We\'ll escape ^_ ^ special chars...@{u}')
  1168. def test_gerrit_reviewers_cmd_line(self):
  1169. self._run_gerrit_upload_test(
  1170. ['-r', 'foo@example.com', '--send-mail'],
  1171. 'desc ✔\n\nBUG=\n\nChange-Id: I123456789',
  1172. ['foo@example.com'],
  1173. squash=False,
  1174. squash_mode='override_nosquash',
  1175. notify=True,
  1176. change_id='I123456789',
  1177. final_description=(
  1178. 'desc ✔\n\nBUG=\nR=foo@example.com\n\nChange-Id: I123456789'))
  1179. def test_gerrit_upload_force_sets_bug(self):
  1180. self._run_gerrit_upload_test(
  1181. ['-b', '10000', '-f'],
  1182. u'desc=\n\nBug: 10000\nChange-Id: Ixxx',
  1183. [],
  1184. force=True,
  1185. expected_upstream_ref='origin/master',
  1186. fetched_description='desc=\n\nChange-Id: Ixxx',
  1187. original_title='Initial upload',
  1188. change_id='Ixxx')
  1189. def test_gerrit_upload_corrects_wrong_change_id(self):
  1190. self._run_gerrit_upload_test(
  1191. ['-b', '10000', '-m', 'Title', '--edit-description'],
  1192. u'desc=\n\nBug: 10000\nChange-Id: Ixxxx',
  1193. [],
  1194. issue='123456',
  1195. expected_upstream_ref='origin/master',
  1196. edit_description='desc=\n\nBug: 10000\nChange-Id: Izzzz',
  1197. fetched_description='desc=\n\nChange-Id: Ixxxx',
  1198. original_title='Title',
  1199. title='Title',
  1200. change_id='Ixxxx')
  1201. def test_gerrit_upload_force_sets_fixed(self):
  1202. self._run_gerrit_upload_test(
  1203. ['-x', '10000', '-f'],
  1204. u'desc=\n\nFixed: 10000\nChange-Id: Ixxx',
  1205. [],
  1206. force=True,
  1207. expected_upstream_ref='origin/master',
  1208. fetched_description='desc=\n\nChange-Id: Ixxx',
  1209. original_title='Initial upload',
  1210. change_id='Ixxx')
  1211. def test_gerrit_reviewer_multiple(self):
  1212. mock.patch('git_cl.gerrit_util.GetCodeReviewTbrScore',
  1213. lambda *a: self._mocked_call('GetCodeReviewTbrScore', *a)).start()
  1214. self._run_gerrit_upload_test(
  1215. [],
  1216. 'desc ✔\nTBR=reviewer@example.com\nBUG=\nR=another@example.com\n'
  1217. 'CC=more@example.com,people@example.com\n\n'
  1218. 'Change-Id: 123456789',
  1219. ['reviewer@example.com', 'another@example.com'],
  1220. expected_upstream_ref='origin/master',
  1221. cc=['more@example.com', 'people@example.com'],
  1222. tbr='reviewer@example.com',
  1223. labels={'Code-Review': 2},
  1224. change_id='123456789',
  1225. original_title='Initial upload')
  1226. def test_gerrit_upload_squash_first_is_default(self):
  1227. self._run_gerrit_upload_test(
  1228. [],
  1229. 'desc ✔\nBUG=\n\nChange-Id: 123456789',
  1230. [],
  1231. expected_upstream_ref='origin/master',
  1232. change_id='123456789',
  1233. original_title='Initial upload')
  1234. def test_gerrit_upload_squash_first(self):
  1235. self._run_gerrit_upload_test(
  1236. ['--squash'],
  1237. 'desc ✔\nBUG=\n\nChange-Id: 123456789',
  1238. [],
  1239. squash=True,
  1240. expected_upstream_ref='origin/master',
  1241. change_id='123456789',
  1242. original_title='Initial upload')
  1243. def test_gerrit_upload_squash_first_with_labels(self):
  1244. self._run_gerrit_upload_test(
  1245. ['--squash', '--cq-dry-run', '--enable-auto-submit'],
  1246. 'desc ✔\nBUG=\n\nChange-Id: 123456789',
  1247. [],
  1248. squash=True,
  1249. expected_upstream_ref='origin/master',
  1250. labels={'Commit-Queue': 1, 'Auto-Submit': 1},
  1251. change_id='123456789',
  1252. original_title='Initial upload')
  1253. def test_gerrit_upload_squash_first_against_rev(self):
  1254. custom_cl_base = 'custom_cl_base_rev_or_branch'
  1255. self._run_gerrit_upload_test(
  1256. ['--squash', custom_cl_base],
  1257. 'desc ✔\nBUG=\n\nChange-Id: 123456789',
  1258. [],
  1259. squash=True,
  1260. expected_upstream_ref='origin/master',
  1261. custom_cl_base=custom_cl_base,
  1262. change_id='123456789',
  1263. original_title='Initial upload')
  1264. self.assertIn(
  1265. 'If you proceed with upload, more than 1 CL may be created by Gerrit',
  1266. sys.stdout.getvalue())
  1267. def test_gerrit_upload_squash_reupload(self):
  1268. description = 'desc ✔\nBUG=\n\nChange-Id: 123456789'
  1269. self._run_gerrit_upload_test(
  1270. ['--squash'],
  1271. description,
  1272. [],
  1273. squash=True,
  1274. expected_upstream_ref='origin/master',
  1275. issue=123456,
  1276. change_id='123456789',
  1277. original_title='User input')
  1278. @mock.patch('sys.stderr', StringIO())
  1279. def test_gerrit_upload_squash_reupload_to_abandoned(self):
  1280. description = 'desc ✔\nBUG=\n\nChange-Id: 123456789'
  1281. with self.assertRaises(SystemExitMock):
  1282. self._run_gerrit_upload_test(
  1283. ['--squash'],
  1284. description,
  1285. [],
  1286. squash=True,
  1287. expected_upstream_ref='origin/master',
  1288. issue=123456,
  1289. fetched_status='ABANDONED',
  1290. change_id='123456789')
  1291. self.assertEqual(
  1292. 'Change https://chromium-review.googlesource.com/123456 has been '
  1293. 'abandoned, new uploads are not allowed\n',
  1294. sys.stderr.getvalue())
  1295. @mock.patch(
  1296. 'gerrit_util.GetAccountDetails',
  1297. return_value={'email': 'yet-another@example.com'})
  1298. def test_gerrit_upload_squash_reupload_to_not_owned(self, _mock):
  1299. description = 'desc ✔\nBUG=\n\nChange-Id: 123456789'
  1300. self._run_gerrit_upload_test(
  1301. ['--squash'],
  1302. description,
  1303. [],
  1304. squash=True,
  1305. expected_upstream_ref='origin/master',
  1306. issue=123456,
  1307. other_cl_owner='other@example.com',
  1308. change_id='123456789',
  1309. original_title='User input')
  1310. self.assertIn(
  1311. 'WARNING: Change 123456 is owned by other@example.com, but you '
  1312. 'authenticate to Gerrit as yet-another@example.com.\n'
  1313. 'Uploading may fail due to lack of permissions',
  1314. sys.stdout.getvalue())
  1315. def test_upload_change_description_editor(self):
  1316. fetched_description = 'foo\n\nChange-Id: 123456789'
  1317. description = 'bar\n\nChange-Id: 123456789'
  1318. self._run_gerrit_upload_test(
  1319. ['--squash', '--edit-description'],
  1320. description,
  1321. [],
  1322. fetched_description=fetched_description,
  1323. squash=True,
  1324. expected_upstream_ref='origin/master',
  1325. issue=123456,
  1326. change_id='123456789',
  1327. original_title='User input',
  1328. edit_description=description)
  1329. @mock.patch('git_cl.RunGit')
  1330. @mock.patch('git_cl.CMDupload')
  1331. @mock.patch('sys.stdin', StringIO('\n'))
  1332. @mock.patch('sys.stdout', StringIO())
  1333. def test_upload_branch_deps(self, *_mocks):
  1334. def mock_run_git(*args, **_kwargs):
  1335. if args[0] == ['for-each-ref',
  1336. '--format=%(refname:short) %(upstream:short)',
  1337. 'refs/heads']:
  1338. # Create a local branch dependency tree that looks like this:
  1339. # test1 -> test2 -> test3 -> test4 -> test5
  1340. # -> test3.1
  1341. # test6 -> test0
  1342. branch_deps = [
  1343. 'test2 test1', # test1 -> test2
  1344. 'test3 test2', # test2 -> test3
  1345. 'test3.1 test2', # test2 -> test3.1
  1346. 'test4 test3', # test3 -> test4
  1347. 'test5 test4', # test4 -> test5
  1348. 'test6 test0', # test0 -> test6
  1349. 'test7', # test7
  1350. ]
  1351. return '\n'.join(branch_deps)
  1352. git_cl.RunGit.side_effect = mock_run_git
  1353. git_cl.CMDupload.return_value = 0
  1354. class MockChangelist():
  1355. def __init__(self):
  1356. pass
  1357. def GetBranch(self):
  1358. return 'test1'
  1359. def GetIssue(self):
  1360. return '123'
  1361. def GetPatchset(self):
  1362. return '1001'
  1363. def IsGerrit(self):
  1364. return False
  1365. ret = git_cl.upload_branch_deps(MockChangelist(), [])
  1366. # CMDupload should have been called 5 times because of 5 dependent branches.
  1367. self.assertEqual(5, len(git_cl.CMDupload.mock_calls))
  1368. self.assertIn(
  1369. 'This command will checkout all dependent branches '
  1370. 'and run "git cl upload". Press Enter to continue, '
  1371. 'or Ctrl+C to abort',
  1372. sys.stdout.getvalue())
  1373. self.assertEqual(0, ret)
  1374. def test_gerrit_change_id(self):
  1375. self.calls = [
  1376. ((['git', 'write-tree'], ),
  1377. 'hashtree'),
  1378. ((['git', 'rev-parse', 'HEAD~0'], ),
  1379. 'branch-parent'),
  1380. ((['git', 'var', 'GIT_AUTHOR_IDENT'], ),
  1381. 'A B <a@b.org> 1456848326 +0100'),
  1382. ((['git', 'var', 'GIT_COMMITTER_IDENT'], ),
  1383. 'C D <c@d.org> 1456858326 +0100'),
  1384. ((['git', 'hash-object', '-t', 'commit', '--stdin'], ),
  1385. 'hashchange'),
  1386. ]
  1387. change_id = git_cl.GenerateGerritChangeId('line1\nline2\n')
  1388. self.assertEqual(change_id, 'Ihashchange')
  1389. def test_desecription_append_footer(self):
  1390. for init_desc, footer_line, expected_desc in [
  1391. # Use unique desc first lines for easy test failure identification.
  1392. ('foo', 'R=one', 'foo\n\nR=one'),
  1393. ('foo\n\nR=one', 'BUG=', 'foo\n\nR=one\nBUG='),
  1394. ('foo\n\nR=one', 'Change-Id: Ixx', 'foo\n\nR=one\n\nChange-Id: Ixx'),
  1395. ('foo\n\nChange-Id: Ixx', 'R=one', 'foo\n\nR=one\n\nChange-Id: Ixx'),
  1396. ('foo\n\nR=one\n\nChange-Id: Ixx', 'TBR=two',
  1397. 'foo\n\nR=one\nTBR=two\n\nChange-Id: Ixx'),
  1398. ('foo\n\nR=one\n\nChange-Id: Ixx', 'Foo-Bar: baz',
  1399. 'foo\n\nR=one\n\nChange-Id: Ixx\nFoo-Bar: baz'),
  1400. ('foo\n\nChange-Id: Ixx', 'Foo-Bak: baz',
  1401. 'foo\n\nChange-Id: Ixx\nFoo-Bak: baz'),
  1402. ('foo', 'Change-Id: Ixx', 'foo\n\nChange-Id: Ixx'),
  1403. ]:
  1404. desc = git_cl.ChangeDescription(init_desc)
  1405. desc.append_footer(footer_line)
  1406. self.assertEqual(desc.description, expected_desc)
  1407. def test_update_reviewers(self):
  1408. data = [
  1409. ('foo', [], [],
  1410. 'foo'),
  1411. ('foo\nR=xx', [], [],
  1412. 'foo\nR=xx'),
  1413. ('foo\nTBR=xx', [], [],
  1414. 'foo\nTBR=xx'),
  1415. ('foo', ['a@c'], [],
  1416. 'foo\n\nR=a@c'),
  1417. ('foo\nR=xx', ['a@c'], [],
  1418. 'foo\n\nR=a@c, xx'),
  1419. ('foo\nTBR=xx', ['a@c'], [],
  1420. 'foo\n\nR=a@c\nTBR=xx'),
  1421. ('foo\nTBR=xx\nR=yy', ['a@c'], [],
  1422. 'foo\n\nR=a@c, yy\nTBR=xx'),
  1423. ('foo\nBUG=', ['a@c'], [],
  1424. 'foo\nBUG=\nR=a@c'),
  1425. ('foo\nR=xx\nTBR=yy\nR=bar', ['a@c'], [],
  1426. 'foo\n\nR=a@c, bar, xx\nTBR=yy'),
  1427. ('foo', ['a@c', 'b@c'], [],
  1428. 'foo\n\nR=a@c, b@c'),
  1429. ('foo\nBar\n\nR=\nBUG=', ['c@c'], [],
  1430. 'foo\nBar\n\nR=c@c\nBUG='),
  1431. ('foo\nBar\n\nR=\nBUG=\nR=', ['c@c'], [],
  1432. 'foo\nBar\n\nR=c@c\nBUG='),
  1433. # Same as the line before, but full of whitespaces.
  1434. (
  1435. 'foo\nBar\n\n R = \n BUG = \n R = ', ['c@c'], [],
  1436. 'foo\nBar\n\nR=c@c\n BUG =',
  1437. ),
  1438. # Whitespaces aren't interpreted as new lines.
  1439. ('foo BUG=allo R=joe ', ['c@c'], [],
  1440. 'foo BUG=allo R=joe\n\nR=c@c'),
  1441. # Redundant TBRs get promoted to Rs
  1442. ('foo\n\nR=a@c\nTBR=t@c', ['b@c', 'a@c'], ['a@c', 't@c'],
  1443. 'foo\n\nR=a@c, b@c\nTBR=t@c'),
  1444. ]
  1445. expected = [i[-1] for i in data]
  1446. actual = []
  1447. for orig, reviewers, tbrs, _expected in data:
  1448. obj = git_cl.ChangeDescription(orig)
  1449. obj.update_reviewers(reviewers, tbrs, None, None, None)
  1450. actual.append(obj.description)
  1451. self.assertEqual(expected, actual)
  1452. def test_get_hash_tags(self):
  1453. cases = [
  1454. ('', []),
  1455. ('a', []),
  1456. ('[a]', ['a']),
  1457. ('[aa]', ['aa']),
  1458. ('[a ]', ['a']),
  1459. ('[a- ]', ['a']),
  1460. ('[a- b]', ['a-b']),
  1461. ('[a--b]', ['a-b']),
  1462. ('[a', []),
  1463. ('[a]x', ['a']),
  1464. ('[aa]x', ['aa']),
  1465. ('[a b]', ['a-b']),
  1466. ('[a b]', ['a-b']),
  1467. ('[a__b]', ['a-b']),
  1468. ('[a] x', ['a']),
  1469. ('[a][b]', ['a', 'b']),
  1470. ('[a] [b]', ['a', 'b']),
  1471. ('[a][b]x', ['a', 'b']),
  1472. ('[a][b] x', ['a', 'b']),
  1473. ('[a]\n[b]', ['a']),
  1474. ('[a\nb]', []),
  1475. ('[a][', ['a']),
  1476. ('Revert "[a] feature"', ['a']),
  1477. ('Reland "[a] feature"', ['a']),
  1478. ('Revert: [a] feature', ['a']),
  1479. ('Reland: [a] feature', ['a']),
  1480. ('Revert "Reland: [a] feature"', ['a']),
  1481. ('Foo: feature', ['foo']),
  1482. ('Foo Bar: feature', ['foo-bar']),
  1483. ('Change Foo::Bar', []),
  1484. ('Foo: Change Foo::Bar', ['foo']),
  1485. ('Revert "Foo bar: feature"', ['foo-bar']),
  1486. ('Reland "Foo bar: feature"', ['foo-bar']),
  1487. ]
  1488. for desc, expected in cases:
  1489. change_desc = git_cl.ChangeDescription(desc)
  1490. actual = change_desc.get_hash_tags()
  1491. self.assertEqual(
  1492. actual,
  1493. expected,
  1494. 'GetHashTags(%r) == %r, expected %r' % (desc, actual, expected))
  1495. self.assertEqual(None, git_cl.GetTargetRef('origin', None, 'master'))
  1496. self.assertEqual(None, git_cl.GetTargetRef(None,
  1497. 'refs/remotes/origin/master',
  1498. 'master'))
  1499. # Check default target refs for branches.
  1500. self.assertEqual('refs/heads/master',
  1501. git_cl.GetTargetRef('origin', 'refs/remotes/origin/master',
  1502. None))
  1503. self.assertEqual('refs/heads/master',
  1504. git_cl.GetTargetRef('origin', 'refs/remotes/origin/lkgr',
  1505. None))
  1506. self.assertEqual('refs/heads/master',
  1507. git_cl.GetTargetRef('origin', 'refs/remotes/origin/lkcr',
  1508. None))
  1509. self.assertEqual('refs/branch-heads/123',
  1510. git_cl.GetTargetRef('origin',
  1511. 'refs/remotes/branch-heads/123',
  1512. None))
  1513. self.assertEqual('refs/diff/test',
  1514. git_cl.GetTargetRef('origin',
  1515. 'refs/remotes/origin/refs/diff/test',
  1516. None))
  1517. self.assertEqual('refs/heads/chrome/m42',
  1518. git_cl.GetTargetRef('origin',
  1519. 'refs/remotes/origin/chrome/m42',
  1520. None))
  1521. # Check target refs for user-specified target branch.
  1522. for branch in ('branch-heads/123', 'remotes/branch-heads/123',
  1523. 'refs/remotes/branch-heads/123'):
  1524. self.assertEqual('refs/branch-heads/123',
  1525. git_cl.GetTargetRef('origin',
  1526. 'refs/remotes/origin/master',
  1527. branch))
  1528. for branch in ('origin/master', 'remotes/origin/master',
  1529. 'refs/remotes/origin/master'):
  1530. self.assertEqual('refs/heads/master',
  1531. git_cl.GetTargetRef('origin',
  1532. 'refs/remotes/branch-heads/123',
  1533. branch))
  1534. for branch in ('master', 'heads/master', 'refs/heads/master'):
  1535. self.assertEqual('refs/heads/master',
  1536. git_cl.GetTargetRef('origin',
  1537. 'refs/remotes/branch-heads/123',
  1538. branch))
  1539. @mock.patch('git_common.is_dirty_git_tree', return_value=True)
  1540. def test_patch_when_dirty(self, *_mocks):
  1541. # Patch when local tree is dirty.
  1542. self.assertNotEqual(git_cl.main(['patch', '123456']), 0)
  1543. def assertIssueAndPatchset(
  1544. self, branch='master', issue='123456', patchset='7',
  1545. git_short_host='chromium'):
  1546. self.assertEqual(
  1547. issue, scm.GIT.GetBranchConfig('', branch, 'gerritissue'))
  1548. self.assertEqual(
  1549. patchset, scm.GIT.GetBranchConfig('', branch, 'gerritpatchset'))
  1550. self.assertEqual(
  1551. 'https://%s-review.googlesource.com' % git_short_host,
  1552. scm.GIT.GetBranchConfig('', branch, 'gerritserver'))
  1553. def _patch_common(self, git_short_host='chromium'):
  1554. mock.patch('git_cl.IsGitVersionAtLeast', return_value=True).start()
  1555. self.mockGit.config['remote.origin.url'] = (
  1556. 'https://%s.googlesource.com/my/repo' % git_short_host)
  1557. gerrit_util.GetChangeDetail.return_value = {
  1558. 'current_revision': '7777777777',
  1559. 'revisions': {
  1560. '1111111111': {
  1561. '_number': 1,
  1562. 'fetch': {'http': {
  1563. 'url': 'https://%s.googlesource.com/my/repo' % git_short_host,
  1564. 'ref': 'refs/changes/56/123456/1',
  1565. }},
  1566. },
  1567. '7777777777': {
  1568. '_number': 7,
  1569. 'fetch': {'http': {
  1570. 'url': 'https://%s.googlesource.com/my/repo' % git_short_host,
  1571. 'ref': 'refs/changes/56/123456/7',
  1572. }},
  1573. },
  1574. },
  1575. }
  1576. def test_patch_gerrit_default(self):
  1577. self._patch_common()
  1578. self.calls += [
  1579. ((['git', 'fetch', 'https://chromium.googlesource.com/my/repo',
  1580. 'refs/changes/56/123456/7'],), ''),
  1581. ((['git', 'cherry-pick', 'FETCH_HEAD'],), ''),
  1582. ((['git', 'rev-parse', 'FETCH_HEAD'],), 'deadbeef'),
  1583. ]
  1584. self.assertEqual(git_cl.main(['patch', '123456']), 0)
  1585. self.assertIssueAndPatchset()
  1586. def test_patch_gerrit_new_branch(self):
  1587. self._patch_common()
  1588. self.calls += [
  1589. ((['git', 'fetch', 'https://chromium.googlesource.com/my/repo',
  1590. 'refs/changes/56/123456/7'],), ''),
  1591. ((['git', 'cherry-pick', 'FETCH_HEAD'],), ''),
  1592. ((['git', 'rev-parse', 'FETCH_HEAD'],), 'deadbeef'),
  1593. ]
  1594. self.assertEqual(git_cl.main(['patch', '-b', 'feature', '123456']), 0)
  1595. self.assertIssueAndPatchset(branch='feature')
  1596. def test_patch_gerrit_force(self):
  1597. self._patch_common('host')
  1598. self.calls += [
  1599. ((['git', 'fetch', 'https://host.googlesource.com/my/repo',
  1600. 'refs/changes/56/123456/7'],), ''),
  1601. ((['git', 'reset', '--hard', 'FETCH_HEAD'],), ''),
  1602. ((['git', 'rev-parse', 'FETCH_HEAD'],), 'deadbeef'),
  1603. ]
  1604. self.assertEqual(git_cl.main(['patch', '123456', '--force']), 0)
  1605. self.assertIssueAndPatchset(git_short_host='host')
  1606. def test_patch_gerrit_guess_by_url(self):
  1607. self._patch_common('else')
  1608. self.calls += [
  1609. ((['git', 'fetch', 'https://else.googlesource.com/my/repo',
  1610. 'refs/changes/56/123456/1'],), ''),
  1611. ((['git', 'cherry-pick', 'FETCH_HEAD'],), ''),
  1612. ((['git', 'rev-parse', 'FETCH_HEAD'],), 'deadbeef'),
  1613. ]
  1614. self.assertEqual(git_cl.main(
  1615. ['patch', 'https://else-review.googlesource.com/#/c/123456/1']), 0)
  1616. self.assertIssueAndPatchset(patchset='1', git_short_host='else')
  1617. def test_patch_gerrit_guess_by_url_with_repo(self):
  1618. self._patch_common('else')
  1619. self.calls += [
  1620. ((['git', 'fetch', 'https://else.googlesource.com/my/repo',
  1621. 'refs/changes/56/123456/1'],), ''),
  1622. ((['git', 'cherry-pick', 'FETCH_HEAD'],), ''),
  1623. ((['git', 'rev-parse', 'FETCH_HEAD'],), 'deadbeef'),
  1624. ]
  1625. self.assertEqual(git_cl.main(
  1626. ['patch', 'https://else-review.googlesource.com/c/my/repo/+/123456/1']),
  1627. 0)
  1628. self.assertIssueAndPatchset(patchset='1', git_short_host='else')
  1629. @mock.patch('sys.stderr', StringIO())
  1630. def test_patch_gerrit_conflict(self):
  1631. self._patch_common()
  1632. self.calls += [
  1633. ((['git', 'fetch', 'https://chromium.googlesource.com/my/repo',
  1634. 'refs/changes/56/123456/7'],), ''),
  1635. ((['git', 'cherry-pick', 'FETCH_HEAD'],), CERR1),
  1636. ]
  1637. with self.assertRaises(SystemExitMock):
  1638. git_cl.main(['patch', '123456'])
  1639. self.assertEqual(
  1640. 'Command "git cherry-pick FETCH_HEAD" failed.\n\n',
  1641. sys.stderr.getvalue())
  1642. @mock.patch(
  1643. 'gerrit_util.GetChangeDetail',
  1644. side_effect=gerrit_util.GerritError(404, ''))
  1645. @mock.patch('sys.stderr', StringIO())
  1646. def test_patch_gerrit_not_exists(self, *_mocks):
  1647. self.mockGit.config['remote.origin.url'] = (
  1648. 'https://chromium.googlesource.com/my/repo')
  1649. with self.assertRaises(SystemExitMock):
  1650. self.assertEqual(1, git_cl.main(['patch', '123456']))
  1651. self.assertEqual(
  1652. 'change 123456 at https://chromium-review.googlesource.com does not '
  1653. 'exist or you have no access to it\n',
  1654. sys.stderr.getvalue())
  1655. def _checkout_calls(self):
  1656. return [
  1657. ((['git', 'config', '--local', '--get-regexp',
  1658. 'branch\\..*\\.gerritissue'], ),
  1659. ('branch.ger-branch.gerritissue 123456\n'
  1660. 'branch.gbranch654.gerritissue 654321\n')),
  1661. ]
  1662. def test_checkout_gerrit(self):
  1663. """Tests git cl checkout <issue>."""
  1664. self.calls = self._checkout_calls()
  1665. self.calls += [((['git', 'checkout', 'ger-branch'], ), '')]
  1666. self.assertEqual(0, git_cl.main(['checkout', '123456']))
  1667. def test_checkout_not_found(self):
  1668. """Tests git cl checkout <issue>."""
  1669. self.calls = self._checkout_calls()
  1670. self.assertEqual(1, git_cl.main(['checkout', '99999']))
  1671. def test_checkout_no_branch_issues(self):
  1672. """Tests git cl checkout <issue>."""
  1673. self.calls = [
  1674. ((['git', 'config', '--local', '--get-regexp',
  1675. 'branch\\..*\\.gerritissue'], ), CERR1),
  1676. ]
  1677. self.assertEqual(1, git_cl.main(['checkout', '99999']))
  1678. def _test_gerrit_ensure_authenticated_common(self, auth):
  1679. mock.patch(
  1680. 'git_cl.ask_for_data',
  1681. lambda prompt: self._mocked_call('ask_for_data', prompt)).start()
  1682. mock.patch('git_cl.gerrit_util.CookiesAuthenticator',
  1683. CookiesAuthenticatorMockFactory(hosts_with_creds=auth)).start()
  1684. self.mockGit.config['remote.origin.url'] = (
  1685. 'https://chromium.googlesource.com/my/repo')
  1686. cl = git_cl.Changelist()
  1687. cl.branch = 'master'
  1688. cl.branchref = 'refs/heads/master'
  1689. return cl
  1690. @mock.patch('sys.stderr', StringIO())
  1691. def test_gerrit_ensure_authenticated_missing(self):
  1692. cl = self._test_gerrit_ensure_authenticated_common(auth={
  1693. 'chromium.googlesource.com': ('git-is.ok', '', 'but gerrit is missing'),
  1694. })
  1695. with self.assertRaises(SystemExitMock):
  1696. cl.EnsureAuthenticated(force=False)
  1697. self.assertEqual(
  1698. 'Credentials for the following hosts are required:\n'
  1699. ' chromium-review.googlesource.com\n'
  1700. 'These are read from ~/.gitcookies (or legacy ~/.netrc)\n'
  1701. 'You can (re)generate your credentials by visiting '
  1702. 'https://chromium-review.googlesource.com/new-password\n',
  1703. sys.stderr.getvalue())
  1704. def test_gerrit_ensure_authenticated_conflict(self):
  1705. cl = self._test_gerrit_ensure_authenticated_common(auth={
  1706. 'chromium.googlesource.com':
  1707. ('git-one.example.com', None, 'secret1'),
  1708. 'chromium-review.googlesource.com':
  1709. ('git-other.example.com', None, 'secret2'),
  1710. })
  1711. self.calls.append(
  1712. (('ask_for_data', 'If you know what you are doing '
  1713. 'press Enter to continue, or Ctrl+C to abort'), ''))
  1714. self.assertIsNone(cl.EnsureAuthenticated(force=False))
  1715. def test_gerrit_ensure_authenticated_ok(self):
  1716. cl = self._test_gerrit_ensure_authenticated_common(auth={
  1717. 'chromium.googlesource.com':
  1718. ('git-same.example.com', None, 'secret'),
  1719. 'chromium-review.googlesource.com':
  1720. ('git-same.example.com', None, 'secret'),
  1721. })
  1722. self.assertIsNone(cl.EnsureAuthenticated(force=False))
  1723. def test_gerrit_ensure_authenticated_skipped(self):
  1724. self.mockGit.config['gerrit.skip-ensure-authenticated'] = 'true'
  1725. cl = self._test_gerrit_ensure_authenticated_common(auth={})
  1726. self.assertIsNone(cl.EnsureAuthenticated(force=False))
  1727. def test_gerrit_ensure_authenticated_bearer_token(self):
  1728. cl = self._test_gerrit_ensure_authenticated_common(auth={
  1729. 'chromium.googlesource.com':
  1730. ('', None, 'secret'),
  1731. 'chromium-review.googlesource.com':
  1732. ('', None, 'secret'),
  1733. })
  1734. self.assertIsNone(cl.EnsureAuthenticated(force=False))
  1735. header = gerrit_util.CookiesAuthenticator().get_auth_header(
  1736. 'chromium.googlesource.com')
  1737. self.assertTrue('Bearer' in header)
  1738. def test_gerrit_ensure_authenticated_non_https(self):
  1739. self.mockGit.config['remote.origin.url'] = 'custom-scheme://repo'
  1740. self.calls = [
  1741. (('logging.warning',
  1742. 'Ignoring branch %(branch)s with non-https remote '
  1743. '%(remote)s', {
  1744. 'branch': 'master',
  1745. 'remote': 'custom-scheme://repo'}
  1746. ), None),
  1747. ]
  1748. mock.patch('git_cl.gerrit_util.CookiesAuthenticator',
  1749. CookiesAuthenticatorMockFactory(hosts_with_creds={})).start()
  1750. mock.patch('logging.warning',
  1751. lambda *a: self._mocked_call('logging.warning', *a)).start()
  1752. cl = git_cl.Changelist()
  1753. cl.branch = 'master'
  1754. cl.branchref = 'refs/heads/master'
  1755. cl.lookedup_issue = True
  1756. self.assertIsNone(cl.EnsureAuthenticated(force=False))
  1757. def test_gerrit_ensure_authenticated_non_url(self):
  1758. self.mockGit.config['remote.origin.url'] = (
  1759. 'git@somehost.example:foo/bar.git')
  1760. self.calls = [
  1761. (('logging.error',
  1762. 'Remote "%(remote)s" for branch "%(branch)s" points to "%(url)s", '
  1763. 'but it doesn\'t exist.', {
  1764. 'remote': 'origin',
  1765. 'branch': 'master',
  1766. 'url': 'git@somehost.example:foo/bar.git'}
  1767. ), None),
  1768. ]
  1769. mock.patch('git_cl.gerrit_util.CookiesAuthenticator',
  1770. CookiesAuthenticatorMockFactory(hosts_with_creds={})).start()
  1771. mock.patch('logging.error',
  1772. lambda *a: self._mocked_call('logging.error', *a)).start()
  1773. cl = git_cl.Changelist()
  1774. cl.branch = 'master'
  1775. cl.branchref = 'refs/heads/master'
  1776. cl.lookedup_issue = True
  1777. self.assertIsNone(cl.EnsureAuthenticated(force=False))
  1778. def _cmd_set_commit_gerrit_common(self, vote, notify=None):
  1779. self.mockGit.config['branch.master.gerritissue'] = '123'
  1780. self.mockGit.config['branch.master.gerritserver'] = (
  1781. 'https://chromium-review.googlesource.com')
  1782. self.mockGit.config['remote.origin.url'] = (
  1783. 'https://chromium.googlesource.com/infra/infra')
  1784. self.calls = [
  1785. (('SetReview', 'chromium-review.googlesource.com',
  1786. 'infra%2Finfra~123', None,
  1787. {'Commit-Queue': vote}, notify, None), ''),
  1788. ]
  1789. def test_cmd_set_commit_gerrit_clear(self):
  1790. self._cmd_set_commit_gerrit_common(0)
  1791. self.assertEqual(0, git_cl.main(['set-commit', '-c']))
  1792. def test_cmd_set_commit_gerrit_dry(self):
  1793. self._cmd_set_commit_gerrit_common(1, notify=False)
  1794. self.assertEqual(0, git_cl.main(['set-commit', '-d']))
  1795. def test_cmd_set_commit_gerrit(self):
  1796. self._cmd_set_commit_gerrit_common(2)
  1797. self.assertEqual(0, git_cl.main(['set-commit']))
  1798. def test_description_display(self):
  1799. mock.patch('git_cl.Changelist', ChangelistMock).start()
  1800. ChangelistMock.desc = 'foo\n'
  1801. self.assertEqual(0, git_cl.main(['description', '-d']))
  1802. self.assertEqual('foo\n', sys.stdout.getvalue())
  1803. @mock.patch('sys.stderr', StringIO())
  1804. def test_StatusFieldOverrideIssueMissingArgs(self):
  1805. try:
  1806. self.assertEqual(git_cl.main(['status', '--issue', '1']), 0)
  1807. except SystemExitMock:
  1808. self.assertIn(
  1809. '--field must be given when --issue is set.', sys.stderr.getvalue())
  1810. def test_StatusFieldOverrideIssue(self):
  1811. def assertIssue(cl_self, *_args):
  1812. self.assertEqual(cl_self.issue, 1)
  1813. return 'foobar'
  1814. mock.patch('git_cl.Changelist.FetchDescription', assertIssue).start()
  1815. self.assertEqual(
  1816. git_cl.main(['status', '--issue', '1', '--field', 'desc']),
  1817. 0)
  1818. self.assertEqual(sys.stdout.getvalue(), 'foobar\n')
  1819. def test_SetCloseOverrideIssue(self):
  1820. def assertIssue(cl_self, *_args):
  1821. self.assertEqual(cl_self.issue, 1)
  1822. return 'foobar'
  1823. mock.patch('git_cl.Changelist.FetchDescription', assertIssue).start()
  1824. mock.patch('git_cl.Changelist.CloseIssue', lambda *_: None).start()
  1825. self.assertEqual(
  1826. git_cl.main(['set-close', '--issue', '1']), 0)
  1827. def test_description(self):
  1828. self.mockGit.config['remote.origin.url'] = (
  1829. 'https://chromium.googlesource.com/my/repo')
  1830. gerrit_util.GetChangeDetail.return_value = {
  1831. 'current_revision': 'sha1',
  1832. 'revisions': {'sha1': {
  1833. 'commit': {'message': 'foobar'},
  1834. }},
  1835. }
  1836. self.assertEqual(0, git_cl.main([
  1837. 'description',
  1838. 'https://chromium-review.googlesource.com/c/my/repo/+/123123',
  1839. '-d']))
  1840. self.assertEqual('foobar\n', sys.stdout.getvalue())
  1841. def test_description_set_raw(self):
  1842. mock.patch('git_cl.Changelist', ChangelistMock).start()
  1843. mock.patch('git_cl.sys.stdin', StringIO('hihi')).start()
  1844. self.assertEqual(0, git_cl.main(['description', '-n', 'hihi']))
  1845. self.assertEqual('hihi', ChangelistMock.desc)
  1846. def test_description_appends_bug_line(self):
  1847. current_desc = 'Some.\n\nChange-Id: xxx'
  1848. def RunEditor(desc, _, **kwargs):
  1849. self.assertEqual(
  1850. '# Enter a description of the change.\n'
  1851. '# This will be displayed on the codereview site.\n'
  1852. '# The first line will also be used as the subject of the review.\n'
  1853. '#--------------------This line is 72 characters long'
  1854. '--------------------\n'
  1855. 'Some.\n\nChange-Id: xxx\nBug: ',
  1856. desc)
  1857. # Simulate user changing something.
  1858. return 'Some.\n\nChange-Id: xxx\nBug: 123'
  1859. def UpdateDescription(_, desc, force=False):
  1860. self.assertEqual(desc, 'Some.\n\nChange-Id: xxx\nBug: 123')
  1861. mock.patch('git_cl.Changelist.FetchDescription',
  1862. lambda *args: current_desc).start()
  1863. mock.patch('git_cl.Changelist.UpdateDescription',
  1864. UpdateDescription).start()
  1865. mock.patch('git_cl.gclient_utils.RunEditor', RunEditor).start()
  1866. self.mockGit.config['branch.master.gerritissue'] = '123'
  1867. self.assertEqual(0, git_cl.main(['description']))
  1868. def test_description_does_not_append_bug_line_if_fixed_is_present(self):
  1869. current_desc = 'Some.\n\nFixed: 123\nChange-Id: xxx'
  1870. def RunEditor(desc, _, **kwargs):
  1871. self.assertEqual(
  1872. '# Enter a description of the change.\n'
  1873. '# This will be displayed on the codereview site.\n'
  1874. '# The first line will also be used as the subject of the review.\n'
  1875. '#--------------------This line is 72 characters long'
  1876. '--------------------\n'
  1877. 'Some.\n\nFixed: 123\nChange-Id: xxx',
  1878. desc)
  1879. return desc
  1880. mock.patch('git_cl.Changelist.FetchDescription',
  1881. lambda *args: current_desc).start()
  1882. mock.patch('git_cl.gclient_utils.RunEditor', RunEditor).start()
  1883. self.mockGit.config['branch.master.gerritissue'] = '123'
  1884. self.assertEqual(0, git_cl.main(['description']))
  1885. def test_description_set_stdin(self):
  1886. mock.patch('git_cl.Changelist', ChangelistMock).start()
  1887. mock.patch('git_cl.sys.stdin', StringIO('hi \r\n\t there\n\nman')).start()
  1888. self.assertEqual(0, git_cl.main(['description', '-n', '-']))
  1889. self.assertEqual('hi\n\t there\n\nman', ChangelistMock.desc)
  1890. def test_archive(self):
  1891. self.calls = [
  1892. ((['git', 'for-each-ref', '--format=%(refname)', 'refs/heads'],),
  1893. 'refs/heads/master\nrefs/heads/foo\nrefs/heads/bar'),
  1894. ((['git', 'for-each-ref', '--format=%(refname)', 'refs/tags'],), ''),
  1895. ((['git', 'tag', 'git-cl-archived-456-foo', 'foo'],), ''),
  1896. ((['git', 'branch', '-D', 'foo'],), '')
  1897. ]
  1898. mock.patch('git_cl.get_cl_statuses',
  1899. lambda branches, fine_grained, max_processes:
  1900. [(MockChangelistWithBranchAndIssue('master', 1), 'open'),
  1901. (MockChangelistWithBranchAndIssue('foo', 456), 'closed'),
  1902. (MockChangelistWithBranchAndIssue('bar', 789), 'open')]).start()
  1903. self.assertEqual(0, git_cl.main(['archive', '-f']))
  1904. def test_archive_tag_collision(self):
  1905. self.calls = [
  1906. ((['git', 'for-each-ref', '--format=%(refname)', 'refs/heads'],),
  1907. 'refs/heads/master\nrefs/heads/foo\nrefs/heads/bar'),
  1908. ((['git', 'for-each-ref', '--format=%(refname)', 'refs/tags'],),
  1909. 'refs/tags/git-cl-archived-456-foo'),
  1910. ((['git', 'tag', 'git-cl-archived-456-foo-2', 'foo'],), ''),
  1911. ((['git', 'branch', '-D', 'foo'],), '')
  1912. ]
  1913. mock.patch('git_cl.get_cl_statuses',
  1914. lambda branches, fine_grained, max_processes:
  1915. [(MockChangelistWithBranchAndIssue('master', 1), 'open'),
  1916. (MockChangelistWithBranchAndIssue('foo', 456), 'closed'),
  1917. (MockChangelistWithBranchAndIssue('bar', 789), 'open')]).start()
  1918. self.assertEqual(0, git_cl.main(['archive', '-f']))
  1919. def test_archive_current_branch_fails(self):
  1920. self.calls = [
  1921. ((['git', 'for-each-ref', '--format=%(refname)', 'refs/heads'],),
  1922. 'refs/heads/master'),
  1923. ((['git', 'for-each-ref', '--format=%(refname)', 'refs/tags'],), ''),
  1924. ]
  1925. mock.patch('git_cl.get_cl_statuses',
  1926. lambda branches, fine_grained, max_processes:
  1927. [(MockChangelistWithBranchAndIssue('master', 1),
  1928. 'closed')]).start()
  1929. self.assertEqual(1, git_cl.main(['archive', '-f']))
  1930. def test_archive_dry_run(self):
  1931. self.calls = [
  1932. ((['git', 'for-each-ref', '--format=%(refname)', 'refs/heads'],),
  1933. 'refs/heads/master\nrefs/heads/foo\nrefs/heads/bar'),
  1934. ((['git', 'for-each-ref', '--format=%(refname)', 'refs/tags'],), ''),
  1935. ]
  1936. mock.patch('git_cl.get_cl_statuses',
  1937. lambda branches, fine_grained, max_processes:
  1938. [(MockChangelistWithBranchAndIssue('master', 1), 'open'),
  1939. (MockChangelistWithBranchAndIssue('foo', 456), 'closed'),
  1940. (MockChangelistWithBranchAndIssue('bar', 789), 'open')]).start()
  1941. self.assertEqual(0, git_cl.main(['archive', '-f', '--dry-run']))
  1942. def test_archive_no_tags(self):
  1943. self.calls = [
  1944. ((['git', 'for-each-ref', '--format=%(refname)', 'refs/heads'],),
  1945. 'refs/heads/master\nrefs/heads/foo\nrefs/heads/bar'),
  1946. ((['git', 'for-each-ref', '--format=%(refname)', 'refs/tags'],), ''),
  1947. ((['git', 'branch', '-D', 'foo'],), '')
  1948. ]
  1949. mock.patch('git_cl.get_cl_statuses',
  1950. lambda branches, fine_grained, max_processes:
  1951. [(MockChangelistWithBranchAndIssue('master', 1), 'open'),
  1952. (MockChangelistWithBranchAndIssue('foo', 456), 'closed'),
  1953. (MockChangelistWithBranchAndIssue('bar', 789), 'open')]).start()
  1954. self.assertEqual(0, git_cl.main(['archive', '-f', '--notags']))
  1955. def test_archive_tag_cleanup_on_branch_deletion_error(self):
  1956. self.calls = [
  1957. ((['git', 'for-each-ref', '--format=%(refname)', 'refs/heads'],),
  1958. 'refs/heads/master\nrefs/heads/foo\nrefs/heads/bar'),
  1959. ((['git', 'for-each-ref', '--format=%(refname)', 'refs/tags'],), ''),
  1960. ((['git', 'tag', 'git-cl-archived-456-foo', 'foo'],),
  1961. 'refs/tags/git-cl-archived-456-foo'),
  1962. ((['git', 'branch', '-D', 'foo'],), CERR1),
  1963. ((['git', 'tag', '-d', 'git-cl-archived-456-foo'],),
  1964. 'refs/tags/git-cl-archived-456-foo'),
  1965. ]
  1966. mock.patch('git_cl.get_cl_statuses',
  1967. lambda branches, fine_grained, max_processes:
  1968. [(MockChangelistWithBranchAndIssue('master', 1), 'open'),
  1969. (MockChangelistWithBranchAndIssue('foo', 456), 'closed'),
  1970. (MockChangelistWithBranchAndIssue('bar', 789), 'open')]).start()
  1971. self.assertEqual(0, git_cl.main(['archive', '-f']))
  1972. def test_cmd_issue_erase_existing(self):
  1973. self.mockGit.config['branch.master.gerritissue'] = '123'
  1974. self.mockGit.config['branch.master.gerritserver'] = (
  1975. 'https://chromium-review.googlesource.com')
  1976. self.calls = [
  1977. ((['git', 'log', '-1', '--format=%B'],), 'This is a description'),
  1978. ]
  1979. self.assertEqual(0, git_cl.main(['issue', '0']))
  1980. self.assertNotIn('branch.master.gerritissue', self.mockGit.config)
  1981. self.assertNotIn('branch.master.gerritserver', self.mockGit.config)
  1982. def test_cmd_issue_erase_existing_with_change_id(self):
  1983. self.mockGit.config['branch.master.gerritissue'] = '123'
  1984. self.mockGit.config['branch.master.gerritserver'] = (
  1985. 'https://chromium-review.googlesource.com')
  1986. mock.patch('git_cl.Changelist.FetchDescription',
  1987. lambda _: 'This is a description\n\nChange-Id: Ideadbeef').start()
  1988. self.calls = [
  1989. ((['git', 'log', '-1', '--format=%B'],),
  1990. 'This is a description\n\nChange-Id: Ideadbeef'),
  1991. ((['git', 'commit', '--amend', '-m', 'This is a description\n'],), ''),
  1992. ]
  1993. self.assertEqual(0, git_cl.main(['issue', '0']))
  1994. self.assertNotIn('branch.master.gerritissue', self.mockGit.config)
  1995. self.assertNotIn('branch.master.gerritserver', self.mockGit.config)
  1996. def test_cmd_issue_json(self):
  1997. self.mockGit.config['branch.master.gerritissue'] = '123'
  1998. self.mockGit.config['branch.master.gerritserver'] = (
  1999. 'https://chromium-review.googlesource.com')
  2000. self.calls = [
  2001. (('write_json', 'output.json',
  2002. {'issue': 123,
  2003. 'issue_url': 'https://chromium-review.googlesource.com/123'}),
  2004. ''),
  2005. ]
  2006. self.assertEqual(0, git_cl.main(['issue', '--json', 'output.json']))
  2007. def _common_GerritCommitMsgHookCheck(self):
  2008. mock.patch(
  2009. 'git_cl.os.path.abspath',
  2010. lambda path: self._mocked_call(['abspath', path])).start()
  2011. mock.patch(
  2012. 'git_cl.os.path.exists',
  2013. lambda path: self._mocked_call(['exists', path])).start()
  2014. mock.patch(
  2015. 'git_cl.gclient_utils.FileRead',
  2016. lambda path: self._mocked_call(['FileRead', path])).start()
  2017. mock.patch(
  2018. 'git_cl.gclient_utils.rm_file_or_tree',
  2019. lambda path: self._mocked_call(['rm_file_or_tree', path])).start()
  2020. mock.patch(
  2021. 'git_cl.ask_for_data',
  2022. lambda prompt: self._mocked_call('ask_for_data', prompt)).start()
  2023. return git_cl.Changelist(issue=123)
  2024. def test_GerritCommitMsgHookCheck_custom_hook(self):
  2025. cl = self._common_GerritCommitMsgHookCheck()
  2026. self.calls += [
  2027. ((['exists', '.git/hooks/commit-msg'],), True),
  2028. ((['FileRead', '.git/hooks/commit-msg'],),
  2029. '#!/bin/sh\necho "custom hook"')
  2030. ]
  2031. cl._GerritCommitMsgHookCheck(offer_removal=True)
  2032. def test_GerritCommitMsgHookCheck_not_exists(self):
  2033. cl = self._common_GerritCommitMsgHookCheck()
  2034. self.calls += [
  2035. ((['exists', '.git/hooks/commit-msg'],), False),
  2036. ]
  2037. cl._GerritCommitMsgHookCheck(offer_removal=True)
  2038. def test_GerritCommitMsgHookCheck(self):
  2039. cl = self._common_GerritCommitMsgHookCheck()
  2040. self.calls += [
  2041. ((['exists', '.git/hooks/commit-msg'],), True),
  2042. ((['FileRead', '.git/hooks/commit-msg'],),
  2043. '...\n# From Gerrit Code Review\n...\nadd_ChangeId()\n'),
  2044. (('ask_for_data', 'Do you want to remove it now? [Yes/No]: '), 'Yes'),
  2045. ((['rm_file_or_tree', '.git/hooks/commit-msg'],),
  2046. ''),
  2047. ]
  2048. cl._GerritCommitMsgHookCheck(offer_removal=True)
  2049. def test_GerritCmdLand(self):
  2050. self.mockGit.config['branch.master.gerritsquashhash'] = 'deadbeaf'
  2051. self.mockGit.config['branch.master.gerritserver'] = (
  2052. 'chromium-review.googlesource.com')
  2053. self.calls += [
  2054. ((['git', 'diff', 'deadbeaf'],), ''), # No diff.
  2055. ]
  2056. cl = git_cl.Changelist(issue=123)
  2057. cl._GetChangeDetail = lambda *args, **kwargs: {
  2058. 'labels': {},
  2059. 'current_revision': 'deadbeaf',
  2060. }
  2061. cl._GetChangeCommit = lambda: {
  2062. 'commit': 'deadbeef',
  2063. 'web_links': [{'name': 'gitiles',
  2064. 'url': 'https://git.googlesource.com/test/+/deadbeef'}],
  2065. }
  2066. cl.SubmitIssue = lambda wait_for_merge: None
  2067. self.assertEqual(0, cl.CMDLand(force=True,
  2068. bypass_hooks=True,
  2069. verbose=True,
  2070. parallel=False))
  2071. self.assertIn(
  2072. 'Issue chromium-review.googlesource.com/123 has been submitted',
  2073. sys.stdout.getvalue())
  2074. self.assertIn(
  2075. 'Landed as: https://git.googlesource.com/test/+/deadbeef',
  2076. sys.stdout.getvalue())
  2077. def _mock_gerrit_changes_for_detail_cache(self):
  2078. mock.patch('git_cl.Changelist._GetGerritHost', lambda _: 'host').start()
  2079. def test_gerrit_change_detail_cache_simple(self):
  2080. self._mock_gerrit_changes_for_detail_cache()
  2081. gerrit_util.GetChangeDetail.side_effect = ['a', 'b']
  2082. cl1 = git_cl.Changelist(issue=1)
  2083. cl1._cached_remote_url = (
  2084. True, 'https://chromium.googlesource.com/a/my/repo.git/')
  2085. cl2 = git_cl.Changelist(issue=2)
  2086. cl2._cached_remote_url = (
  2087. True, 'https://chromium.googlesource.com/ab/repo')
  2088. self.assertEqual(cl1._GetChangeDetail(), 'a') # Miss.
  2089. self.assertEqual(cl1._GetChangeDetail(), 'a')
  2090. self.assertEqual(cl2._GetChangeDetail(), 'b') # Miss.
  2091. def test_gerrit_change_detail_cache_options(self):
  2092. self._mock_gerrit_changes_for_detail_cache()
  2093. gerrit_util.GetChangeDetail.side_effect = ['cab', 'ad']
  2094. cl = git_cl.Changelist(issue=1)
  2095. cl._cached_remote_url = (True, 'https://chromium.googlesource.com/repo/')
  2096. self.assertEqual(cl._GetChangeDetail(options=['C', 'A', 'B']), 'cab')
  2097. self.assertEqual(cl._GetChangeDetail(options=['A', 'B', 'C']), 'cab')
  2098. self.assertEqual(cl._GetChangeDetail(options=['B', 'A']), 'cab')
  2099. self.assertEqual(cl._GetChangeDetail(options=['C']), 'cab')
  2100. self.assertEqual(cl._GetChangeDetail(options=['A']), 'cab')
  2101. self.assertEqual(cl._GetChangeDetail(), 'cab')
  2102. self.assertEqual(cl._GetChangeDetail(options=['A', 'D']), 'ad')
  2103. self.assertEqual(cl._GetChangeDetail(options=['A']), 'cab')
  2104. self.assertEqual(cl._GetChangeDetail(options=['D']), 'ad')
  2105. self.assertEqual(cl._GetChangeDetail(), 'cab')
  2106. def test_gerrit_description_caching(self):
  2107. gerrit_util.GetChangeDetail.return_value = {
  2108. 'current_revision': 'rev1',
  2109. 'revisions': {
  2110. 'rev1': {'commit': {'message': 'desc1'}},
  2111. },
  2112. }
  2113. self._mock_gerrit_changes_for_detail_cache()
  2114. cl = git_cl.Changelist(issue=1)
  2115. cl._cached_remote_url = (
  2116. True, 'https://chromium.googlesource.com/a/my/repo.git/')
  2117. self.assertEqual(cl.FetchDescription(), 'desc1')
  2118. self.assertEqual(cl.FetchDescription(), 'desc1') # cache hit.
  2119. def test_print_current_creds(self):
  2120. class CookiesAuthenticatorMock(object):
  2121. def __init__(self):
  2122. self.gitcookies = {
  2123. 'host.googlesource.com': ('user', 'pass'),
  2124. 'host-review.googlesource.com': ('user', 'pass'),
  2125. }
  2126. self.netrc = self
  2127. self.netrc.hosts = {
  2128. 'github.com': ('user2', None, 'pass2'),
  2129. 'host2.googlesource.com': ('user3', None, 'pass'),
  2130. }
  2131. mock.patch('git_cl.gerrit_util.CookiesAuthenticator',
  2132. CookiesAuthenticatorMock).start()
  2133. git_cl._GitCookiesChecker().print_current_creds(include_netrc=True)
  2134. self.assertEqual(list(sys.stdout.getvalue().splitlines()), [
  2135. ' Host\t User\t Which file',
  2136. '============================\t=====\t===========',
  2137. 'host-review.googlesource.com\t user\t.gitcookies',
  2138. ' host.googlesource.com\t user\t.gitcookies',
  2139. ' host2.googlesource.com\tuser3\t .netrc',
  2140. ])
  2141. sys.stdout.seek(0)
  2142. sys.stdout.truncate(0)
  2143. git_cl._GitCookiesChecker().print_current_creds(include_netrc=False)
  2144. self.assertEqual(list(sys.stdout.getvalue().splitlines()), [
  2145. ' Host\tUser\t Which file',
  2146. '============================\t====\t===========',
  2147. 'host-review.googlesource.com\tuser\t.gitcookies',
  2148. ' host.googlesource.com\tuser\t.gitcookies',
  2149. ])
  2150. def _common_creds_check_mocks(self):
  2151. def exists_mock(path):
  2152. dirname = os.path.dirname(path)
  2153. if dirname == os.path.expanduser('~'):
  2154. dirname = '~'
  2155. base = os.path.basename(path)
  2156. if base in ('.netrc', '.gitcookies'):
  2157. return self._mocked_call('os.path.exists', '%s/%s' % (dirname, base))
  2158. # git cl also checks for existence other files not relevant to this test.
  2159. return None
  2160. mock.patch(
  2161. 'git_cl.ask_for_data',
  2162. lambda prompt: self._mocked_call('ask_for_data', prompt)).start()
  2163. mock.patch('os.path.exists', exists_mock).start()
  2164. def test_creds_check_gitcookies_not_configured(self):
  2165. self._common_creds_check_mocks()
  2166. mock.patch('git_cl._GitCookiesChecker.get_hosts_with_creds',
  2167. lambda _, include_netrc=False: []).start()
  2168. self.calls = [
  2169. ((['git', 'config', '--path', 'http.cookiefile'],), CERR1),
  2170. ((['git', 'config', '--global', 'http.cookiefile'],), CERR1),
  2171. (('os.path.exists', '~/.netrc'), True),
  2172. (('ask_for_data', 'Press Enter to setup .gitcookies, '
  2173. 'or Ctrl+C to abort'), ''),
  2174. ((['git', 'config', '--global', 'http.cookiefile',
  2175. os.path.expanduser('~/.gitcookies')], ), ''),
  2176. ]
  2177. self.assertEqual(0, git_cl.main(['creds-check']))
  2178. self.assertTrue(
  2179. sys.stdout.getvalue().startswith(
  2180. 'You seem to be using outdated .netrc for git credentials:'))
  2181. self.assertIn(
  2182. '\nConfigured git to use .gitcookies from',
  2183. sys.stdout.getvalue())
  2184. def test_creds_check_gitcookies_configured_custom_broken(self):
  2185. self._common_creds_check_mocks()
  2186. mock.patch('git_cl._GitCookiesChecker.get_hosts_with_creds',
  2187. lambda _, include_netrc=False: []).start()
  2188. self.calls = [
  2189. ((['git', 'config', '--path', 'http.cookiefile'],), CERR1),
  2190. ((['git', 'config', '--global', 'http.cookiefile'],),
  2191. '/custom/.gitcookies'),
  2192. (('os.path.exists', '/custom/.gitcookies'), False),
  2193. (('ask_for_data', 'Reconfigure git to use default .gitcookies? '
  2194. 'Press Enter to reconfigure, or Ctrl+C to abort'), ''),
  2195. ((['git', 'config', '--global', 'http.cookiefile',
  2196. os.path.expanduser('~/.gitcookies')], ), ''),
  2197. ]
  2198. self.assertEqual(0, git_cl.main(['creds-check']))
  2199. self.assertIn(
  2200. 'WARNING: You have configured custom path to .gitcookies: ',
  2201. sys.stdout.getvalue())
  2202. self.assertIn(
  2203. 'However, your configured .gitcookies file is missing.',
  2204. sys.stdout.getvalue())
  2205. def test_git_cl_comment_add_gerrit(self):
  2206. self.mockGit.branchref = None
  2207. self.mockGit.config['remote.origin.url'] = (
  2208. 'https://chromium.googlesource.com/infra/infra')
  2209. self.calls = [
  2210. (('SetReview', 'chromium-review.googlesource.com', 'infra%2Finfra~10',
  2211. 'msg', None, None, None),
  2212. None),
  2213. ]
  2214. self.assertEqual(0, git_cl.main(['comment', '-i', '10', '-a', 'msg']))
  2215. @mock.patch('git_cl.Changelist.GetBranch', return_value='foo')
  2216. def test_git_cl_comments_fetch_gerrit(self, *_mocks):
  2217. self.mockGit.config['remote.origin.url'] = (
  2218. 'https://chromium.googlesource.com/infra/infra')
  2219. gerrit_util.GetChangeDetail.return_value = {
  2220. 'owner': {'email': 'owner@example.com'},
  2221. 'current_revision': 'ba5eba11',
  2222. 'revisions': {
  2223. 'deadbeaf': {
  2224. '_number': 1,
  2225. },
  2226. 'ba5eba11': {
  2227. '_number': 2,
  2228. },
  2229. },
  2230. 'messages': [
  2231. {
  2232. u'_revision_number': 1,
  2233. u'author': {
  2234. u'_account_id': 1111084,
  2235. u'email': u'could-be-anything@example.com',
  2236. u'name': u'LUCI CQ'
  2237. },
  2238. u'date': u'2017-03-15 20:08:45.000000000',
  2239. u'id': u'f5a6c25ecbd3b3b54a43ae418ed97eff046dc50b',
  2240. u'message': u'Patch Set 1:\n\nDry run: CQ is trying the patch...',
  2241. u'tag': u'autogenerated:cq:dry-run'
  2242. },
  2243. {
  2244. u'_revision_number': 2,
  2245. u'author': {
  2246. u'_account_id': 11151243,
  2247. u'email': u'owner@example.com',
  2248. u'name': u'owner'
  2249. },
  2250. u'date': u'2017-03-16 20:00:41.000000000',
  2251. u'id': u'f5a6c25ecbd3b3b54a43ae418ed97eff046d1234',
  2252. u'message': u'PTAL',
  2253. },
  2254. {
  2255. u'_revision_number': 2,
  2256. u'author': {
  2257. u'_account_id': 148512,
  2258. u'email': u'reviewer@example.com',
  2259. u'name': u'reviewer'
  2260. },
  2261. u'date': u'2017-03-17 05:19:37.500000000',
  2262. u'id': u'f5a6c25ecbd3b3b54a43ae418ed97eff046d4568',
  2263. u'message': u'Patch Set 2: Code-Review+1',
  2264. },
  2265. ]
  2266. }
  2267. self.calls = [
  2268. (('GetChangeComments', 'chromium-review.googlesource.com',
  2269. 'infra%2Finfra~1'), {
  2270. '/COMMIT_MSG': [
  2271. {
  2272. 'author': {'email': u'reviewer@example.com'},
  2273. 'updated': u'2017-03-17 05:19:37.500000000',
  2274. 'patch_set': 2,
  2275. 'side': 'REVISION',
  2276. 'message': 'Please include a bug link',
  2277. },
  2278. ],
  2279. 'codereview.settings': [
  2280. {
  2281. 'author': {'email': u'owner@example.com'},
  2282. 'updated': u'2017-03-16 20:00:41.000000000',
  2283. 'patch_set': 2,
  2284. 'side': 'PARENT',
  2285. 'line': 42,
  2286. 'message': 'I removed this because it is bad',
  2287. },
  2288. ]
  2289. }),
  2290. (('GetChangeRobotComments', 'chromium-review.googlesource.com',
  2291. 'infra%2Finfra~1'), {}),
  2292. ] * 2 + [
  2293. (('write_json', 'output.json', [
  2294. {
  2295. u'date': u'2017-03-16 20:00:41.000000',
  2296. u'message': (
  2297. u'PTAL\n' +
  2298. u'\n' +
  2299. u'codereview.settings\n' +
  2300. u' Base, Line 42: https://chromium-review.googlesource.com/' +
  2301. u'c/1/2/codereview.settings#b42\n' +
  2302. u' I removed this because it is bad\n'),
  2303. u'autogenerated': False,
  2304. u'approval': False,
  2305. u'disapproval': False,
  2306. u'sender': u'owner@example.com'
  2307. }, {
  2308. u'date': u'2017-03-17 05:19:37.500000',
  2309. u'message': (
  2310. u'Patch Set 2: Code-Review+1\n' +
  2311. u'\n' +
  2312. u'/COMMIT_MSG\n' +
  2313. u' PS2, File comment: https://chromium-review.googlesource' +
  2314. u'.com/c/1/2//COMMIT_MSG#\n' +
  2315. u' Please include a bug link\n'),
  2316. u'autogenerated': False,
  2317. u'approval': False,
  2318. u'disapproval': False,
  2319. u'sender': u'reviewer@example.com'
  2320. }
  2321. ]), '')
  2322. ]
  2323. expected_comments_summary = [
  2324. git_cl._CommentSummary(
  2325. message=(
  2326. u'PTAL\n' +
  2327. u'\n' +
  2328. u'codereview.settings\n' +
  2329. u' Base, Line 42: https://chromium-review.googlesource.com/' +
  2330. u'c/1/2/codereview.settings#b42\n' +
  2331. u' I removed this because it is bad\n'),
  2332. date=datetime.datetime(2017, 3, 16, 20, 0, 41, 0),
  2333. autogenerated=False,
  2334. disapproval=False, approval=False, sender=u'owner@example.com'),
  2335. git_cl._CommentSummary(
  2336. message=(
  2337. u'Patch Set 2: Code-Review+1\n' +
  2338. u'\n' +
  2339. u'/COMMIT_MSG\n' +
  2340. u' PS2, File comment: https://chromium-review.googlesource.com/' +
  2341. u'c/1/2//COMMIT_MSG#\n' +
  2342. u' Please include a bug link\n'),
  2343. date=datetime.datetime(2017, 3, 17, 5, 19, 37, 500000),
  2344. autogenerated=False,
  2345. disapproval=False, approval=False, sender=u'reviewer@example.com'),
  2346. ]
  2347. cl = git_cl.Changelist(
  2348. issue=1, branchref='refs/heads/foo')
  2349. self.assertEqual(cl.GetCommentsSummary(), expected_comments_summary)
  2350. self.assertEqual(
  2351. 0, git_cl.main(['comments', '-i', '1', '-j', 'output.json']))
  2352. def test_git_cl_comments_robot_comments(self):
  2353. # git cl comments also fetches robot comments (which are considered a type
  2354. # of autogenerated comment), and unlike other types of comments, only robot
  2355. # comments from the latest patchset are shown.
  2356. self.mockGit.config['remote.origin.url'] = (
  2357. 'https://chromium.googlesource.com/infra/infra')
  2358. gerrit_util.GetChangeDetail.return_value = {
  2359. 'owner': {'email': 'owner@example.com'},
  2360. 'current_revision': 'ba5eba11',
  2361. 'revisions': {
  2362. 'deadbeaf': {
  2363. '_number': 1,
  2364. },
  2365. 'ba5eba11': {
  2366. '_number': 2,
  2367. },
  2368. },
  2369. 'messages': [
  2370. {
  2371. u'_revision_number': 1,
  2372. u'author': {
  2373. u'_account_id': 1111084,
  2374. u'email': u'commit-bot@chromium.org',
  2375. u'name': u'Commit Bot'
  2376. },
  2377. u'date': u'2017-03-15 20:08:45.000000000',
  2378. u'id': u'f5a6c25ecbd3b3b54a43ae418ed97eff046dc50b',
  2379. u'message': u'Patch Set 1:\n\nDry run: CQ is trying the patch...',
  2380. u'tag': u'autogenerated:cq:dry-run'
  2381. },
  2382. {
  2383. u'_revision_number': 1,
  2384. u'author': {
  2385. u'_account_id': 123,
  2386. u'email': u'tricium@serviceaccount.com',
  2387. u'name': u'Tricium'
  2388. },
  2389. u'date': u'2017-03-16 20:00:41.000000000',
  2390. u'id': u'f5a6c25ecbd3b3b54a43ae418ed97eff046d1234',
  2391. u'message': u'(1 comment)',
  2392. u'tag': u'autogenerated:tricium',
  2393. },
  2394. {
  2395. u'_revision_number': 1,
  2396. u'author': {
  2397. u'_account_id': 123,
  2398. u'email': u'tricium@serviceaccount.com',
  2399. u'name': u'Tricium'
  2400. },
  2401. u'date': u'2017-03-16 20:00:41.000000000',
  2402. u'id': u'f5a6c25ecbd3b3b54a43ae418ed97eff046d1234',
  2403. u'message': u'(1 comment)',
  2404. u'tag': u'autogenerated:tricium',
  2405. },
  2406. {
  2407. u'_revision_number': 2,
  2408. u'author': {
  2409. u'_account_id': 123,
  2410. u'email': u'tricium@serviceaccount.com',
  2411. u'name': u'reviewer'
  2412. },
  2413. u'date': u'2017-03-17 05:30:37.000000000',
  2414. u'tag': u'autogenerated:tricium',
  2415. u'id': u'f5a6c25ecbd3b3b54a43ae418ed97eff046d4568',
  2416. u'message': u'(1 comment)',
  2417. },
  2418. ]
  2419. }
  2420. self.calls = [
  2421. (('GetChangeComments', 'chromium-review.googlesource.com',
  2422. 'infra%2Finfra~1'), {}),
  2423. (('GetChangeRobotComments', 'chromium-review.googlesource.com',
  2424. 'infra%2Finfra~1'), {
  2425. 'codereview.settings': [
  2426. {
  2427. u'author': {u'email': u'tricium@serviceaccount.com'},
  2428. u'updated': u'2017-03-17 05:30:37.000000000',
  2429. u'robot_run_id': u'5565031076855808',
  2430. u'robot_id': u'Linter/Category',
  2431. u'tag': u'autogenerated:tricium',
  2432. u'patch_set': 2,
  2433. u'side': u'REVISION',
  2434. u'message': u'Linter warning message text',
  2435. u'line': 32,
  2436. },
  2437. ],
  2438. }),
  2439. ]
  2440. expected_comments_summary = [
  2441. git_cl._CommentSummary(date=datetime.datetime(2017, 3, 17, 5, 30, 37),
  2442. message=(
  2443. u'(1 comment)\n\ncodereview.settings\n'
  2444. u' PS2, Line 32: https://chromium-review.googlesource.com/'
  2445. u'c/1/2/codereview.settings#32\n'
  2446. u' Linter warning message text\n'),
  2447. sender=u'tricium@serviceaccount.com',
  2448. autogenerated=True, approval=False, disapproval=False)
  2449. ]
  2450. cl = git_cl.Changelist(
  2451. issue=1, branchref='refs/heads/foo')
  2452. self.assertEqual(cl.GetCommentsSummary(), expected_comments_summary)
  2453. def test_get_remote_url_with_mirror(self):
  2454. original_os_path_isdir = os.path.isdir
  2455. def selective_os_path_isdir_mock(path):
  2456. if path == '/cache/this-dir-exists':
  2457. return self._mocked_call('os.path.isdir', path)
  2458. return original_os_path_isdir(path)
  2459. mock.patch('os.path.isdir', selective_os_path_isdir_mock).start()
  2460. url = 'https://chromium.googlesource.com/my/repo'
  2461. self.mockGit.config['remote.origin.url'] = (
  2462. '/cache/this-dir-exists')
  2463. self.mockGit.config['/cache/this-dir-exists:remote.origin.url'] = (
  2464. url)
  2465. self.calls = [
  2466. (('os.path.isdir', '/cache/this-dir-exists'),
  2467. True),
  2468. ]
  2469. cl = git_cl.Changelist(issue=1)
  2470. self.assertEqual(cl.GetRemoteUrl(), url)
  2471. self.assertEqual(cl.GetRemoteUrl(), url) # Must be cached.
  2472. def test_get_remote_url_non_existing_mirror(self):
  2473. original_os_path_isdir = os.path.isdir
  2474. def selective_os_path_isdir_mock(path):
  2475. if path == '/cache/this-dir-doesnt-exist':
  2476. return self._mocked_call('os.path.isdir', path)
  2477. return original_os_path_isdir(path)
  2478. mock.patch('os.path.isdir', selective_os_path_isdir_mock).start()
  2479. mock.patch('logging.error',
  2480. lambda *a: self._mocked_call('logging.error', *a)).start()
  2481. self.mockGit.config['remote.origin.url'] = (
  2482. '/cache/this-dir-doesnt-exist')
  2483. self.calls = [
  2484. (('os.path.isdir', '/cache/this-dir-doesnt-exist'),
  2485. False),
  2486. (('logging.error',
  2487. 'Remote "%(remote)s" for branch "%(branch)s" points to "%(url)s", '
  2488. 'but it doesn\'t exist.', {
  2489. 'remote': 'origin',
  2490. 'branch': 'master',
  2491. 'url': '/cache/this-dir-doesnt-exist'}
  2492. ), None),
  2493. ]
  2494. cl = git_cl.Changelist(issue=1)
  2495. self.assertIsNone(cl.GetRemoteUrl())
  2496. def test_get_remote_url_misconfigured_mirror(self):
  2497. original_os_path_isdir = os.path.isdir
  2498. def selective_os_path_isdir_mock(path):
  2499. if path == '/cache/this-dir-exists':
  2500. return self._mocked_call('os.path.isdir', path)
  2501. return original_os_path_isdir(path)
  2502. mock.patch('os.path.isdir', selective_os_path_isdir_mock).start()
  2503. mock.patch('logging.error',
  2504. lambda *a: self._mocked_call('logging.error', *a)).start()
  2505. self.mockGit.config['remote.origin.url'] = (
  2506. '/cache/this-dir-exists')
  2507. self.calls = [
  2508. (('os.path.isdir', '/cache/this-dir-exists'), True),
  2509. (('logging.error',
  2510. 'Remote "%(remote)s" for branch "%(branch)s" points to '
  2511. '"%(cache_path)s", but it is misconfigured.\n'
  2512. '"%(cache_path)s" must be a git repo and must have a remote named '
  2513. '"%(remote)s" pointing to the git host.', {
  2514. 'remote': 'origin',
  2515. 'cache_path': '/cache/this-dir-exists',
  2516. 'branch': 'master'}
  2517. ), None),
  2518. ]
  2519. cl = git_cl.Changelist(issue=1)
  2520. self.assertIsNone(cl.GetRemoteUrl())
  2521. def test_gerrit_change_identifier_with_project(self):
  2522. self.mockGit.config['remote.origin.url'] = (
  2523. 'https://chromium.googlesource.com/a/my/repo.git/')
  2524. cl = git_cl.Changelist(issue=123456)
  2525. self.assertEqual(cl._GerritChangeIdentifier(), 'my%2Frepo~123456')
  2526. def test_gerrit_change_identifier_without_project(self):
  2527. mock.patch('logging.error',
  2528. lambda *a: self._mocked_call('logging.error', *a)).start()
  2529. self.calls = [
  2530. (('logging.error',
  2531. 'Remote "%(remote)s" for branch "%(branch)s" points to "%(url)s", '
  2532. 'but it doesn\'t exist.', {
  2533. 'remote': 'origin',
  2534. 'branch': 'master',
  2535. 'url': ''}
  2536. ), None),
  2537. ]
  2538. cl = git_cl.Changelist(issue=123456)
  2539. self.assertEqual(cl._GerritChangeIdentifier(), '123456')
  2540. class ChangelistTest(unittest.TestCase):
  2541. def setUp(self):
  2542. super(ChangelistTest, self).setUp()
  2543. mock.patch('gclient_utils.FileRead').start()
  2544. mock.patch('gclient_utils.FileWrite').start()
  2545. mock.patch('gclient_utils.temporary_file', TemporaryFileMock()).start()
  2546. mock.patch(
  2547. 'git_cl.Changelist.GetCodereviewServer',
  2548. return_value='https://chromium-review.googlesource.com').start()
  2549. mock.patch('git_cl.Changelist.GetAuthor', return_value='author').start()
  2550. mock.patch('git_cl.Changelist.GetIssue', return_value=123456).start()
  2551. mock.patch('git_cl.Changelist.GetPatchset', return_value=7).start()
  2552. mock.patch('git_cl.PRESUBMIT_SUPPORT', 'PRESUBMIT_SUPPORT').start()
  2553. mock.patch('git_cl.Settings.GetRoot', return_value='root').start()
  2554. mock.patch('git_cl.time_time').start()
  2555. mock.patch('metrics.collector').start()
  2556. mock.patch('subprocess2.Popen').start()
  2557. self.addCleanup(mock.patch.stopall)
  2558. self.temp_count = 0
  2559. def testRunHook(self):
  2560. expected_results = {
  2561. 'more_cc': ['more@example.com', 'cc@example.com'],
  2562. 'should_continue': True,
  2563. }
  2564. gclient_utils.FileRead.return_value = json.dumps(expected_results)
  2565. git_cl.time_time.side_effect = [100, 200]
  2566. mockProcess = mock.Mock()
  2567. mockProcess.wait.return_value = 0
  2568. subprocess2.Popen.return_value = mockProcess
  2569. cl = git_cl.Changelist()
  2570. results = cl.RunHook(
  2571. committing=True,
  2572. may_prompt=True,
  2573. verbose=2,
  2574. parallel=True,
  2575. upstream='upstream',
  2576. description='description',
  2577. all_files=True)
  2578. self.assertEqual(expected_results, results)
  2579. subprocess2.Popen.assert_called_once_with([
  2580. 'vpython', 'PRESUBMIT_SUPPORT',
  2581. '--root', 'root',
  2582. '--upstream', 'upstream',
  2583. '--verbose', '--verbose',
  2584. '--author', 'author',
  2585. '--gerrit_url', 'https://chromium-review.googlesource.com',
  2586. '--issue', '123456',
  2587. '--patchset', '7',
  2588. '--commit',
  2589. '--may_prompt',
  2590. '--parallel',
  2591. '--all_files',
  2592. '--json_output', '/tmp/fake-temp2',
  2593. '--description_file', '/tmp/fake-temp1',
  2594. ])
  2595. gclient_utils.FileWrite.assert_called_once_with(
  2596. '/tmp/fake-temp1', 'description')
  2597. metrics.collector.add_repeated('sub_commands', {
  2598. 'command': 'presubmit',
  2599. 'execution_time': 100,
  2600. 'exit_code': 0,
  2601. })
  2602. def testRunHook_FewerOptions(self):
  2603. expected_results = {
  2604. 'more_cc': ['more@example.com', 'cc@example.com'],
  2605. 'should_continue': True,
  2606. }
  2607. gclient_utils.FileRead.return_value = json.dumps(expected_results)
  2608. git_cl.time_time.side_effect = [100, 200]
  2609. mockProcess = mock.Mock()
  2610. mockProcess.wait.return_value = 0
  2611. subprocess2.Popen.return_value = mockProcess
  2612. git_cl.Changelist.GetAuthor.return_value = None
  2613. git_cl.Changelist.GetIssue.return_value = None
  2614. git_cl.Changelist.GetPatchset.return_value = None
  2615. git_cl.Changelist.GetCodereviewServer.return_value = None
  2616. cl = git_cl.Changelist()
  2617. results = cl.RunHook(
  2618. committing=False,
  2619. may_prompt=False,
  2620. verbose=0,
  2621. parallel=False,
  2622. upstream='upstream',
  2623. description='description',
  2624. all_files=False)
  2625. self.assertEqual(expected_results, results)
  2626. subprocess2.Popen.assert_called_once_with([
  2627. 'vpython', 'PRESUBMIT_SUPPORT',
  2628. '--root', 'root',
  2629. '--upstream', 'upstream',
  2630. '--upload',
  2631. '--json_output', '/tmp/fake-temp2',
  2632. '--description_file', '/tmp/fake-temp1',
  2633. ])
  2634. gclient_utils.FileWrite.assert_called_once_with(
  2635. '/tmp/fake-temp1', 'description')
  2636. metrics.collector.add_repeated('sub_commands', {
  2637. 'command': 'presubmit',
  2638. 'execution_time': 100,
  2639. 'exit_code': 0,
  2640. })
  2641. @mock.patch('sys.exit', side_effect=SystemExitMock)
  2642. def testRunHook_Failure(self, _mock):
  2643. git_cl.time_time.side_effect = [100, 200]
  2644. mockProcess = mock.Mock()
  2645. mockProcess.wait.return_value = 2
  2646. subprocess2.Popen.return_value = mockProcess
  2647. cl = git_cl.Changelist()
  2648. with self.assertRaises(SystemExitMock):
  2649. cl.RunHook(
  2650. committing=True,
  2651. may_prompt=True,
  2652. verbose=2,
  2653. parallel=True,
  2654. upstream='upstream',
  2655. description='description',
  2656. all_files=True)
  2657. sys.exit.assert_called_once_with(2)
  2658. def testRunPostUploadHook(self):
  2659. cl = git_cl.Changelist()
  2660. cl.RunPostUploadHook(2, 'upstream', 'description')
  2661. subprocess2.Popen.assert_called_once_with([
  2662. 'vpython', 'PRESUBMIT_SUPPORT',
  2663. '--root', 'root',
  2664. '--upstream', 'upstream',
  2665. '--verbose', '--verbose',
  2666. '--author', 'author',
  2667. '--gerrit_url', 'https://chromium-review.googlesource.com',
  2668. '--issue', '123456',
  2669. '--patchset', '7',
  2670. '--post_upload',
  2671. '--description_file', '/tmp/fake-temp1',
  2672. ])
  2673. gclient_utils.FileWrite.assert_called_once_with(
  2674. '/tmp/fake-temp1', 'description')
  2675. class CMDTestCaseBase(unittest.TestCase):
  2676. _STATUSES = [
  2677. 'STATUS_UNSPECIFIED', 'SCHEDULED', 'STARTED', 'SUCCESS', 'FAILURE',
  2678. 'INFRA_FAILURE', 'CANCELED',
  2679. ]
  2680. _CHANGE_DETAIL = {
  2681. 'project': 'depot_tools',
  2682. 'status': 'OPEN',
  2683. 'owner': {'email': 'owner@e.mail'},
  2684. 'current_revision': 'beeeeeef',
  2685. 'revisions': {
  2686. 'deadbeaf': {'_number': 6},
  2687. 'beeeeeef': {
  2688. '_number': 7,
  2689. 'fetch': {'http': {
  2690. 'url': 'https://chromium.googlesource.com/depot_tools',
  2691. 'ref': 'refs/changes/56/123456/7'
  2692. }},
  2693. },
  2694. },
  2695. }
  2696. _DEFAULT_RESPONSE = {
  2697. 'builds': [{
  2698. 'id': str(100 + idx),
  2699. 'builder': {
  2700. 'project': 'chromium',
  2701. 'bucket': 'try',
  2702. 'builder': 'bot_' + status.lower(),
  2703. },
  2704. 'createTime': '2019-10-09T08:00:0%d.854286Z' % (idx % 10),
  2705. 'tags': [],
  2706. 'status': status,
  2707. } for idx, status in enumerate(_STATUSES)]
  2708. }
  2709. def setUp(self):
  2710. super(CMDTestCaseBase, self).setUp()
  2711. mock.patch('git_cl.sys.stdout', StringIO()).start()
  2712. mock.patch('git_cl.uuid.uuid4', return_value='uuid4').start()
  2713. mock.patch('git_cl.Changelist.GetIssue', return_value=123456).start()
  2714. mock.patch(
  2715. 'git_cl.Changelist.GetCodereviewServer',
  2716. return_value='https://chromium-review.googlesource.com').start()
  2717. mock.patch(
  2718. 'git_cl.Changelist._GetGerritHost',
  2719. return_value='chromium-review.googlesource.com').start()
  2720. mock.patch(
  2721. 'git_cl.Changelist.GetMostRecentPatchset',
  2722. return_value=7).start()
  2723. mock.patch(
  2724. 'git_cl.Changelist.GetRemoteUrl',
  2725. return_value='https://chromium.googlesource.com/depot_tools').start()
  2726. mock.patch(
  2727. 'auth.Authenticator',
  2728. return_value=AuthenticatorMock()).start()
  2729. mock.patch(
  2730. 'gerrit_util.GetChangeDetail',
  2731. return_value=self._CHANGE_DETAIL).start()
  2732. mock.patch(
  2733. 'git_cl._call_buildbucket',
  2734. return_value = self._DEFAULT_RESPONSE).start()
  2735. mock.patch('git_common.is_dirty_git_tree', return_value=False).start()
  2736. self.addCleanup(mock.patch.stopall)
  2737. class CMDPresubmitTestCase(CMDTestCaseBase):
  2738. def setUp(self):
  2739. super(CMDPresubmitTestCase, self).setUp()
  2740. mock.patch(
  2741. 'git_cl.Changelist.GetCommonAncestorWithUpstream',
  2742. return_value='upstream').start()
  2743. mock.patch(
  2744. 'git_cl.Changelist.FetchDescription',
  2745. return_value='fetch description').start()
  2746. mock.patch(
  2747. 'git_cl._create_description_from_log',
  2748. return_value='get description').start()
  2749. mock.patch('git_cl.Changelist.RunHook').start()
  2750. def testDefaultCase(self):
  2751. self.assertEqual(0, git_cl.main(['presubmit']))
  2752. git_cl.Changelist.RunHook.assert_called_once_with(
  2753. committing=True,
  2754. may_prompt=False,
  2755. verbose=0,
  2756. parallel=None,
  2757. upstream='upstream',
  2758. description='fetch description',
  2759. all_files=None)
  2760. def testNoIssue(self):
  2761. git_cl.Changelist.GetIssue.return_value = None
  2762. self.assertEqual(0, git_cl.main(['presubmit']))
  2763. git_cl.Changelist.RunHook.assert_called_once_with(
  2764. committing=True,
  2765. may_prompt=False,
  2766. verbose=0,
  2767. parallel=None,
  2768. upstream='upstream',
  2769. description='get description',
  2770. all_files=None)
  2771. def testCustomBranch(self):
  2772. self.assertEqual(0, git_cl.main(['presubmit', 'custom_branch']))
  2773. git_cl.Changelist.RunHook.assert_called_once_with(
  2774. committing=True,
  2775. may_prompt=False,
  2776. verbose=0,
  2777. parallel=None,
  2778. upstream='custom_branch',
  2779. description='fetch description',
  2780. all_files=None)
  2781. def testOptions(self):
  2782. self.assertEqual(
  2783. 0, git_cl.main(['presubmit', '-v', '-v', '--all', '--parallel', '-u']))
  2784. git_cl.Changelist.RunHook.assert_called_once_with(
  2785. committing=False,
  2786. may_prompt=False,
  2787. verbose=2,
  2788. parallel=True,
  2789. upstream='upstream',
  2790. description='fetch description',
  2791. all_files=True)
  2792. class CMDTryResultsTestCase(CMDTestCaseBase):
  2793. _DEFAULT_REQUEST = {
  2794. 'predicate': {
  2795. "gerritChanges": [{
  2796. "project": "depot_tools",
  2797. "host": "chromium-review.googlesource.com",
  2798. "patchset": 7,
  2799. "change": 123456,
  2800. }],
  2801. },
  2802. 'fields': ('builds.*.id,builds.*.builder,builds.*.status' +
  2803. ',builds.*.createTime,builds.*.tags'),
  2804. }
  2805. def testNoJobs(self):
  2806. git_cl._call_buildbucket.return_value = {}
  2807. self.assertEqual(0, git_cl.main(['try-results']))
  2808. self.assertEqual('No tryjobs scheduled.\n', sys.stdout.getvalue())
  2809. git_cl._call_buildbucket.assert_called_once_with(
  2810. mock.ANY, 'cr-buildbucket.appspot.com', 'SearchBuilds',
  2811. self._DEFAULT_REQUEST)
  2812. def testPrintToStdout(self):
  2813. self.assertEqual(0, git_cl.main(['try-results']))
  2814. self.assertEqual([
  2815. 'Successes:',
  2816. ' bot_success https://ci.chromium.org/b/103',
  2817. 'Infra Failures:',
  2818. ' bot_infra_failure https://ci.chromium.org/b/105',
  2819. 'Failures:',
  2820. ' bot_failure https://ci.chromium.org/b/104',
  2821. 'Canceled:',
  2822. ' bot_canceled ',
  2823. 'Started:',
  2824. ' bot_started https://ci.chromium.org/b/102',
  2825. 'Scheduled:',
  2826. ' bot_scheduled id=101',
  2827. 'Other:',
  2828. ' bot_status_unspecified id=100',
  2829. 'Total: 7 tryjobs',
  2830. ], sys.stdout.getvalue().splitlines())
  2831. git_cl._call_buildbucket.assert_called_once_with(
  2832. mock.ANY, 'cr-buildbucket.appspot.com', 'SearchBuilds',
  2833. self._DEFAULT_REQUEST)
  2834. def testPrintToStdoutWithMasters(self):
  2835. self.assertEqual(0, git_cl.main(['try-results', '--print-master']))
  2836. self.assertEqual([
  2837. 'Successes:',
  2838. ' try bot_success https://ci.chromium.org/b/103',
  2839. 'Infra Failures:',
  2840. ' try bot_infra_failure https://ci.chromium.org/b/105',
  2841. 'Failures:',
  2842. ' try bot_failure https://ci.chromium.org/b/104',
  2843. 'Canceled:',
  2844. ' try bot_canceled ',
  2845. 'Started:',
  2846. ' try bot_started https://ci.chromium.org/b/102',
  2847. 'Scheduled:',
  2848. ' try bot_scheduled id=101',
  2849. 'Other:',
  2850. ' try bot_status_unspecified id=100',
  2851. 'Total: 7 tryjobs',
  2852. ], sys.stdout.getvalue().splitlines())
  2853. git_cl._call_buildbucket.assert_called_once_with(
  2854. mock.ANY, 'cr-buildbucket.appspot.com', 'SearchBuilds',
  2855. self._DEFAULT_REQUEST)
  2856. @mock.patch('git_cl.write_json')
  2857. def testWriteToJson(self, mockJsonDump):
  2858. self.assertEqual(0, git_cl.main(['try-results', '--json', 'file.json']))
  2859. git_cl._call_buildbucket.assert_called_once_with(
  2860. mock.ANY, 'cr-buildbucket.appspot.com', 'SearchBuilds',
  2861. self._DEFAULT_REQUEST)
  2862. mockJsonDump.assert_called_once_with(
  2863. 'file.json', self._DEFAULT_RESPONSE['builds'])
  2864. def test_filter_failed_for_one_simple(self):
  2865. self.assertEqual([], git_cl._filter_failed_for_retry([]))
  2866. self.assertEqual(
  2867. [
  2868. ('chromium', 'try', 'bot_failure'),
  2869. ('chromium', 'try', 'bot_infra_failure'),
  2870. ],
  2871. git_cl._filter_failed_for_retry(self._DEFAULT_RESPONSE['builds']))
  2872. def test_filter_failed_for_retry_many_builds(self):
  2873. def _build(name, created_sec, status, experimental=False):
  2874. assert 0 <= created_sec < 100, created_sec
  2875. b = {
  2876. 'id': 112112,
  2877. 'builder': {
  2878. 'project': 'chromium',
  2879. 'bucket': 'try',
  2880. 'builder': name,
  2881. },
  2882. 'createTime': '2019-10-09T08:00:%02d.854286Z' % created_sec,
  2883. 'status': status,
  2884. 'tags': [],
  2885. }
  2886. if experimental:
  2887. b['tags'].append({'key': 'cq_experimental', 'value': 'true'})
  2888. return b
  2889. builds = [
  2890. _build('flaky-last-green', 1, 'FAILURE'),
  2891. _build('flaky-last-green', 2, 'SUCCESS'),
  2892. _build('flaky', 1, 'SUCCESS'),
  2893. _build('flaky', 2, 'FAILURE'),
  2894. _build('running', 1, 'FAILED'),
  2895. _build('running', 2, 'SCHEDULED'),
  2896. _build('yep-still-running', 1, 'STARTED'),
  2897. _build('yep-still-running', 2, 'FAILURE'),
  2898. _build('cq-experimental', 1, 'SUCCESS', experimental=True),
  2899. _build('cq-experimental', 2, 'FAILURE', experimental=True),
  2900. # Simulate experimental in CQ builder, which developer decided
  2901. # to retry manually which resulted in 2nd build non-experimental.
  2902. _build('sometimes-experimental', 1, 'FAILURE', experimental=True),
  2903. _build('sometimes-experimental', 2, 'FAILURE', experimental=False),
  2904. ]
  2905. builds.sort(key=lambda b: b['status']) # ~deterministic shuffle.
  2906. self.assertEqual(
  2907. [
  2908. ('chromium', 'try', 'flaky'),
  2909. ('chromium', 'try', 'sometimes-experimental'),
  2910. ],
  2911. git_cl._filter_failed_for_retry(builds))
  2912. class CMDTryTestCase(CMDTestCaseBase):
  2913. @mock.patch('git_cl.Changelist.SetCQState')
  2914. def testSetCQDryRunByDefault(self, mockSetCQState):
  2915. mockSetCQState.return_value = 0
  2916. self.assertEqual(0, git_cl.main(['try']))
  2917. git_cl.Changelist.SetCQState.assert_called_with(git_cl._CQState.DRY_RUN)
  2918. self.assertEqual(
  2919. sys.stdout.getvalue(),
  2920. 'Scheduling CQ dry run on: '
  2921. 'https://chromium-review.googlesource.com/123456\n')
  2922. @mock.patch('git_cl._call_buildbucket')
  2923. def testScheduleOnBuildbucket(self, mockCallBuildbucket):
  2924. mockCallBuildbucket.return_value = {}
  2925. self.assertEqual(0, git_cl.main([
  2926. 'try', '-B', 'luci.chromium.try', '-b', 'win',
  2927. '-p', 'key=val', '-p', 'json=[{"a":1}, null]']))
  2928. self.assertIn(
  2929. 'Scheduling jobs on:\n'
  2930. ' chromium/try: win',
  2931. git_cl.sys.stdout.getvalue())
  2932. expected_request = {
  2933. "requests": [{
  2934. "scheduleBuild": {
  2935. "requestId": "uuid4",
  2936. "builder": {
  2937. "project": "chromium",
  2938. "builder": "win",
  2939. "bucket": "try",
  2940. },
  2941. "gerritChanges": [{
  2942. "project": "depot_tools",
  2943. "host": "chromium-review.googlesource.com",
  2944. "patchset": 7,
  2945. "change": 123456,
  2946. }],
  2947. "properties": {
  2948. "category": "git_cl_try",
  2949. "json": [{"a": 1}, None],
  2950. "key": "val",
  2951. },
  2952. "tags": [
  2953. {"value": "win", "key": "builder"},
  2954. {"value": "git_cl_try", "key": "user_agent"},
  2955. ],
  2956. },
  2957. }],
  2958. }
  2959. mockCallBuildbucket.assert_called_with(
  2960. mock.ANY, 'cr-buildbucket.appspot.com', 'Batch', expected_request)
  2961. @mock.patch('git_cl._call_buildbucket')
  2962. def testScheduleOnBuildbucketWithRevision(self, mockCallBuildbucket):
  2963. mockCallBuildbucket.return_value = {}
  2964. self.assertEqual(0, git_cl.main([
  2965. 'try', '-B', 'luci.chromium.try', '-b', 'win', '-b', 'linux',
  2966. '-p', 'key=val', '-p', 'json=[{"a":1}, null]',
  2967. '-r', 'beeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef']))
  2968. self.assertIn(
  2969. 'Scheduling jobs on:\n'
  2970. ' chromium/try: linux\n'
  2971. ' chromium/try: win',
  2972. git_cl.sys.stdout.getvalue())
  2973. expected_request = {
  2974. "requests": [{
  2975. "scheduleBuild": {
  2976. "requestId": "uuid4",
  2977. "builder": {
  2978. "project": "chromium",
  2979. "builder": "linux",
  2980. "bucket": "try",
  2981. },
  2982. "gerritChanges": [{
  2983. "project": "depot_tools",
  2984. "host": "chromium-review.googlesource.com",
  2985. "patchset": 7,
  2986. "change": 123456,
  2987. }],
  2988. "properties": {
  2989. "category": "git_cl_try",
  2990. "json": [{"a": 1}, None],
  2991. "key": "val",
  2992. },
  2993. "tags": [
  2994. {"value": "linux", "key": "builder"},
  2995. {"value": "git_cl_try", "key": "user_agent"},
  2996. ],
  2997. "gitilesCommit": {
  2998. "host": "chromium-review.googlesource.com",
  2999. "project": "depot_tools",
  3000. "id": "beeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef",
  3001. }
  3002. },
  3003. },
  3004. {
  3005. "scheduleBuild": {
  3006. "requestId": "uuid4",
  3007. "builder": {
  3008. "project": "chromium",
  3009. "builder": "win",
  3010. "bucket": "try",
  3011. },
  3012. "gerritChanges": [{
  3013. "project": "depot_tools",
  3014. "host": "chromium-review.googlesource.com",
  3015. "patchset": 7,
  3016. "change": 123456,
  3017. }],
  3018. "properties": {
  3019. "category": "git_cl_try",
  3020. "json": [{"a": 1}, None],
  3021. "key": "val",
  3022. },
  3023. "tags": [
  3024. {"value": "win", "key": "builder"},
  3025. {"value": "git_cl_try", "key": "user_agent"},
  3026. ],
  3027. "gitilesCommit": {
  3028. "host": "chromium-review.googlesource.com",
  3029. "project": "depot_tools",
  3030. "id": "beeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef",
  3031. }
  3032. },
  3033. }],
  3034. }
  3035. mockCallBuildbucket.assert_called_with(
  3036. mock.ANY, 'cr-buildbucket.appspot.com', 'Batch', expected_request)
  3037. @mock.patch('sys.stderr', StringIO())
  3038. def testScheduleOnBuildbucket_WrongBucket(self):
  3039. with self.assertRaises(SystemExit):
  3040. git_cl.main([
  3041. 'try', '-B', 'not-a-bucket', '-b', 'win',
  3042. '-p', 'key=val', '-p', 'json=[{"a":1}, null]'])
  3043. self.assertIn(
  3044. 'Invalid bucket: not-a-bucket.',
  3045. sys.stderr.getvalue())
  3046. @mock.patch('git_cl._call_buildbucket')
  3047. @mock.patch('git_cl._fetch_tryjobs')
  3048. def testScheduleOnBuildbucketRetryFailed(
  3049. self, mockFetchTryJobs, mockCallBuildbucket):
  3050. git_cl._fetch_tryjobs.side_effect = lambda *_, **kw: {
  3051. 7: [],
  3052. 6: [{
  3053. 'id': 112112,
  3054. 'builder': {
  3055. 'project': 'chromium',
  3056. 'bucket': 'try',
  3057. 'builder': 'linux', },
  3058. 'createTime': '2019-10-09T08:00:01.854286Z',
  3059. 'tags': [],
  3060. 'status': 'FAILURE', }], }[kw['patchset']]
  3061. mockCallBuildbucket.return_value = {}
  3062. self.assertEqual(0, git_cl.main(['try', '--retry-failed']))
  3063. self.assertIn(
  3064. 'Scheduling jobs on:\n'
  3065. ' chromium/try: linux',
  3066. git_cl.sys.stdout.getvalue())
  3067. expected_request = {
  3068. "requests": [{
  3069. "scheduleBuild": {
  3070. "requestId": "uuid4",
  3071. "builder": {
  3072. "project": "chromium",
  3073. "bucket": "try",
  3074. "builder": "linux",
  3075. },
  3076. "gerritChanges": [{
  3077. "project": "depot_tools",
  3078. "host": "chromium-review.googlesource.com",
  3079. "patchset": 7,
  3080. "change": 123456,
  3081. }],
  3082. "properties": {
  3083. "category": "git_cl_try",
  3084. },
  3085. "tags": [
  3086. {"value": "linux", "key": "builder"},
  3087. {"value": "git_cl_try", "key": "user_agent"},
  3088. {"value": "1", "key": "retry_failed"},
  3089. ],
  3090. },
  3091. }],
  3092. }
  3093. mockCallBuildbucket.assert_called_with(
  3094. mock.ANY, 'cr-buildbucket.appspot.com', 'Batch', expected_request)
  3095. def test_parse_bucket(self):
  3096. test_cases = [
  3097. {
  3098. 'bucket': 'chromium/try',
  3099. 'result': ('chromium', 'try'),
  3100. },
  3101. {
  3102. 'bucket': 'luci.chromium.try',
  3103. 'result': ('chromium', 'try'),
  3104. 'has_warning': True,
  3105. },
  3106. {
  3107. 'bucket': 'skia.primary',
  3108. 'result': ('skia', 'skia.primary'),
  3109. 'has_warning': True,
  3110. },
  3111. {
  3112. 'bucket': 'not-a-bucket',
  3113. 'result': (None, None),
  3114. },
  3115. ]
  3116. for test_case in test_cases:
  3117. git_cl.sys.stdout.truncate(0)
  3118. self.assertEqual(
  3119. test_case['result'], git_cl._parse_bucket(test_case['bucket']))
  3120. if test_case.get('has_warning'):
  3121. expected_warning = 'WARNING Please use %s/%s to specify the bucket' % (
  3122. test_case['result'])
  3123. self.assertIn(expected_warning, git_cl.sys.stdout.getvalue())
  3124. class CMDUploadTestCase(CMDTestCaseBase):
  3125. def setUp(self):
  3126. super(CMDUploadTestCase, self).setUp()
  3127. mock.patch('git_cl._fetch_tryjobs').start()
  3128. mock.patch('git_cl._trigger_tryjobs', return_value={}).start()
  3129. mock.patch('git_cl.Changelist.CMDUpload', return_value=0).start()
  3130. mock.patch('git_cl.Settings.GetIsGerrit', return_value=True).start()
  3131. self.addCleanup(mock.patch.stopall)
  3132. def testWarmUpChangeDetailCache(self):
  3133. self.assertEqual(0, git_cl.main(['upload']))
  3134. gerrit_util.GetChangeDetail.assert_called_once_with(
  3135. 'chromium-review.googlesource.com', 'depot_tools~123456',
  3136. frozenset([
  3137. 'LABELS', 'CURRENT_REVISION', 'DETAILED_ACCOUNTS',
  3138. 'CURRENT_COMMIT']))
  3139. def testUploadRetryFailed(self):
  3140. # This test mocks out the actual upload part, and just asserts that after
  3141. # upload, if --retry-failed is added, then the tool will fetch try jobs
  3142. # from the previous patchset and trigger the right builders on the latest
  3143. # patchset.
  3144. git_cl._fetch_tryjobs.side_effect = [
  3145. # Latest patchset: No builds.
  3146. [],
  3147. # Patchset before latest: Some builds.
  3148. [{
  3149. 'id': str(100 + idx),
  3150. 'builder': {
  3151. 'project': 'chromium',
  3152. 'bucket': 'try',
  3153. 'builder': 'bot_' + status.lower(),
  3154. },
  3155. 'createTime': '2019-10-09T08:00:0%d.854286Z' % (idx % 10),
  3156. 'tags': [],
  3157. 'status': status,
  3158. } for idx, status in enumerate(self._STATUSES)],
  3159. ]
  3160. self.assertEqual(0, git_cl.main(['upload', '--retry-failed']))
  3161. self.assertEqual([
  3162. mock.call(mock.ANY, 'cr-buildbucket.appspot.com', patchset=7),
  3163. mock.call(mock.ANY, 'cr-buildbucket.appspot.com', patchset=6),
  3164. ], git_cl._fetch_tryjobs.mock_calls)
  3165. expected_buckets = [
  3166. ('chromium', 'try', 'bot_failure'),
  3167. ('chromium', 'try', 'bot_infra_failure'),
  3168. ]
  3169. git_cl._trigger_tryjobs.assert_called_once_with(mock.ANY, expected_buckets,
  3170. mock.ANY, 8)
  3171. class MakeRequestsHelperTestCase(unittest.TestCase):
  3172. def exampleGerritChange(self):
  3173. return {
  3174. 'host': 'chromium-review.googlesource.com',
  3175. 'project': 'depot_tools',
  3176. 'change': 1,
  3177. 'patchset': 2,
  3178. }
  3179. def testMakeRequestsHelperNoOptions(self):
  3180. # Basic test for the helper function _make_tryjob_schedule_requests;
  3181. # it shouldn't throw AttributeError even when options doesn't have any
  3182. # of the expected values; it will use default option values.
  3183. changelist = ChangelistMock(gerrit_change=self.exampleGerritChange())
  3184. jobs = [('chromium', 'try', 'my-builder')]
  3185. options = optparse.Values()
  3186. requests = git_cl._make_tryjob_schedule_requests(
  3187. changelist, jobs, options, patchset=None)
  3188. # requestId is non-deterministic. Just assert that it's there and has
  3189. # a particular length.
  3190. self.assertEqual(len(requests[0]['scheduleBuild'].pop('requestId')), 36)
  3191. self.assertEqual(requests, [{
  3192. 'scheduleBuild': {
  3193. 'builder': {
  3194. 'bucket': 'try',
  3195. 'builder': 'my-builder',
  3196. 'project': 'chromium'
  3197. },
  3198. 'gerritChanges': [self.exampleGerritChange()],
  3199. 'properties': {
  3200. 'category': 'git_cl_try'
  3201. },
  3202. 'tags': [{
  3203. 'key': 'builder',
  3204. 'value': 'my-builder'
  3205. }, {
  3206. 'key': 'user_agent',
  3207. 'value': 'git_cl_try'
  3208. }]
  3209. }
  3210. }])
  3211. def testMakeRequestsHelperPresubmitSetsDryRunProperty(self):
  3212. changelist = ChangelistMock(gerrit_change=self.exampleGerritChange())
  3213. jobs = [('chromium', 'try', 'presubmit')]
  3214. options = optparse.Values()
  3215. requests = git_cl._make_tryjob_schedule_requests(
  3216. changelist, jobs, options, patchset=None)
  3217. self.assertEqual(requests[0]['scheduleBuild']['properties'], {
  3218. 'category': 'git_cl_try',
  3219. 'dry_run': 'true'
  3220. })
  3221. def testMakeRequestsHelperRevisionSet(self):
  3222. # Gitiles commit is specified when revision is in options.
  3223. changelist = ChangelistMock(gerrit_change=self.exampleGerritChange())
  3224. jobs = [('chromium', 'try', 'my-builder')]
  3225. options = optparse.Values({'revision': 'ba5eba11'})
  3226. requests = git_cl._make_tryjob_schedule_requests(
  3227. changelist, jobs, options, patchset=None)
  3228. self.assertEqual(
  3229. requests[0]['scheduleBuild']['gitilesCommit'], {
  3230. 'host': 'chromium-review.googlesource.com',
  3231. 'id': 'ba5eba11',
  3232. 'project': 'depot_tools'
  3233. })
  3234. def testMakeRequestsHelperRetryFailedSet(self):
  3235. # An extra tag is added when retry_failed is in options.
  3236. changelist = ChangelistMock(gerrit_change=self.exampleGerritChange())
  3237. jobs = [('chromium', 'try', 'my-builder')]
  3238. options = optparse.Values({'retry_failed': 'true'})
  3239. requests = git_cl._make_tryjob_schedule_requests(
  3240. changelist, jobs, options, patchset=None)
  3241. self.assertEqual(
  3242. requests[0]['scheduleBuild']['tags'], [
  3243. {
  3244. 'key': 'builder',
  3245. 'value': 'my-builder'
  3246. },
  3247. {
  3248. 'key': 'user_agent',
  3249. 'value': 'git_cl_try'
  3250. },
  3251. {
  3252. 'key': 'retry_failed',
  3253. 'value': '1'
  3254. }
  3255. ])
  3256. def testMakeRequestsHelperCategorySet(self):
  3257. # The category property can be overriden with options.
  3258. changelist = ChangelistMock(gerrit_change=self.exampleGerritChange())
  3259. jobs = [('chromium', 'try', 'my-builder')]
  3260. options = optparse.Values({'category': 'my-special-category'})
  3261. requests = git_cl._make_tryjob_schedule_requests(
  3262. changelist, jobs, options, patchset=None)
  3263. self.assertEqual(requests[0]['scheduleBuild']['properties'],
  3264. {'category': 'my-special-category'})
  3265. class CMDFormatTestCase(unittest.TestCase):
  3266. def setUp(self):
  3267. super(CMDFormatTestCase, self).setUp()
  3268. mock.patch('git_cl.RunCommand').start()
  3269. mock.patch('clang_format.FindClangFormatToolInChromiumTree').start()
  3270. mock.patch('clang_format.FindClangFormatScriptInChromiumTree').start()
  3271. mock.patch('git_cl.settings').start()
  3272. self._top_dir = tempfile.mkdtemp()
  3273. self.addCleanup(mock.patch.stopall)
  3274. def tearDown(self):
  3275. shutil.rmtree(self._top_dir)
  3276. super(CMDFormatTestCase, self).tearDown()
  3277. def _make_temp_file(self, fname, contents):
  3278. with open(os.path.join(self._top_dir, fname), 'w') as tf:
  3279. tf.write('\n'.join(contents))
  3280. def _make_yapfignore(self, contents):
  3281. self._make_temp_file('.yapfignore', contents)
  3282. def _check_yapf_filtering(self, files, expected):
  3283. self.assertEqual(expected, git_cl._FilterYapfIgnoredFiles(
  3284. files, git_cl._GetYapfIgnorePatterns(self._top_dir)))
  3285. def _run_command_mock(self, return_value):
  3286. def f(*args, **kwargs):
  3287. if 'stdin' in kwargs:
  3288. self.assertIsInstance(kwargs['stdin'], bytes)
  3289. return return_value
  3290. return f
  3291. def testClangFormatDiffFull(self):
  3292. self._make_temp_file('test.cc', ['// test'])
  3293. git_cl.settings.GetFormatFullByDefault.return_value = False
  3294. diff_file = [os.path.join(self._top_dir, 'test.cc')]
  3295. mock_opts = mock.Mock(full=True, dry_run=True, diff=False)
  3296. # Diff
  3297. git_cl.RunCommand.side_effect = self._run_command_mock(' // test')
  3298. return_value = git_cl._RunClangFormatDiff(mock_opts, diff_file,
  3299. self._top_dir, 'HEAD')
  3300. self.assertEqual(2, return_value)
  3301. # No diff
  3302. git_cl.RunCommand.side_effect = self._run_command_mock('// test')
  3303. return_value = git_cl._RunClangFormatDiff(mock_opts, diff_file,
  3304. self._top_dir, 'HEAD')
  3305. self.assertEqual(0, return_value)
  3306. def testClangFormatDiff(self):
  3307. git_cl.settings.GetFormatFullByDefault.return_value = False
  3308. mock_opts = mock.Mock(full=False, dry_run=True, diff=False)
  3309. # Diff
  3310. git_cl.RunCommand.side_effect = self._run_command_mock('error')
  3311. return_value = git_cl._RunClangFormatDiff(
  3312. mock_opts, ['.'], self._top_dir, 'HEAD')
  3313. self.assertEqual(2, return_value)
  3314. # No diff
  3315. git_cl.RunCommand.side_effect = self._run_command_mock('')
  3316. return_value = git_cl._RunClangFormatDiff(mock_opts, ['.'], self._top_dir,
  3317. 'HEAD')
  3318. self.assertEqual(0, return_value)
  3319. def testYapfignoreExplicit(self):
  3320. self._make_yapfignore(['foo/bar.py', 'foo/bar/baz.py'])
  3321. files = [
  3322. 'bar.py',
  3323. 'foo/bar.py',
  3324. 'foo/baz.py',
  3325. 'foo/bar/baz.py',
  3326. 'foo/bar/foobar.py',
  3327. ]
  3328. expected = [
  3329. 'bar.py',
  3330. 'foo/baz.py',
  3331. 'foo/bar/foobar.py',
  3332. ]
  3333. self._check_yapf_filtering(files, expected)
  3334. def testYapfignoreSingleWildcards(self):
  3335. self._make_yapfignore(['*bar.py', 'foo*', 'baz*.py'])
  3336. files = [
  3337. 'bar.py', # Matched by *bar.py.
  3338. 'bar.txt',
  3339. 'foobar.py', # Matched by *bar.py, foo*.
  3340. 'foobar.txt', # Matched by foo*.
  3341. 'bazbar.py', # Matched by *bar.py, baz*.py.
  3342. 'bazbar.txt',
  3343. 'foo/baz.txt', # Matched by foo*.
  3344. 'bar/bar.py', # Matched by *bar.py.
  3345. 'baz/foo.py', # Matched by baz*.py, foo*.
  3346. 'baz/foo.txt',
  3347. ]
  3348. expected = [
  3349. 'bar.txt',
  3350. 'bazbar.txt',
  3351. 'baz/foo.txt',
  3352. ]
  3353. self._check_yapf_filtering(files, expected)
  3354. def testYapfignoreMultiplewildcards(self):
  3355. self._make_yapfignore(['*bar*', '*foo*baz.txt'])
  3356. files = [
  3357. 'bar.py', # Matched by *bar*.
  3358. 'bar.txt', # Matched by *bar*.
  3359. 'abar.py', # Matched by *bar*.
  3360. 'foobaz.txt', # Matched by *foo*baz.txt.
  3361. 'foobaz.py',
  3362. 'afoobaz.txt', # Matched by *foo*baz.txt.
  3363. ]
  3364. expected = [
  3365. 'foobaz.py',
  3366. ]
  3367. self._check_yapf_filtering(files, expected)
  3368. def testYapfignoreComments(self):
  3369. self._make_yapfignore(['test.py', '#test2.py'])
  3370. files = [
  3371. 'test.py',
  3372. 'test2.py',
  3373. ]
  3374. expected = [
  3375. 'test2.py',
  3376. ]
  3377. self._check_yapf_filtering(files, expected)
  3378. def testYapfignoreBlankLines(self):
  3379. self._make_yapfignore(['test.py', '', '', 'test2.py'])
  3380. files = [
  3381. 'test.py',
  3382. 'test2.py',
  3383. 'test3.py',
  3384. ]
  3385. expected = [
  3386. 'test3.py',
  3387. ]
  3388. self._check_yapf_filtering(files, expected)
  3389. def testYapfignoreWhitespace(self):
  3390. self._make_yapfignore([' test.py '])
  3391. files = [
  3392. 'test.py',
  3393. 'test2.py',
  3394. ]
  3395. expected = [
  3396. 'test2.py',
  3397. ]
  3398. self._check_yapf_filtering(files, expected)
  3399. def testYapfignoreNoFiles(self):
  3400. self._make_yapfignore(['test.py'])
  3401. self._check_yapf_filtering([], [])
  3402. def testYapfignoreMissingYapfignore(self):
  3403. files = [
  3404. 'test.py',
  3405. ]
  3406. expected = [
  3407. 'test.py',
  3408. ]
  3409. self._check_yapf_filtering(files, expected)
  3410. if __name__ == '__main__':
  3411. logging.basicConfig(
  3412. level=logging.DEBUG if '-v' in sys.argv else logging.ERROR)
  3413. unittest.main()