git_cl_test.py 134 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493
  1. #!/usr/bin/env python
  2. # Copyright (c) 2012 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. """Unit tests for git_cl.py."""
  6. import contextlib
  7. import datetime
  8. import json
  9. import logging
  10. import os
  11. import StringIO
  12. import sys
  13. import tempfile
  14. import unittest
  15. import urlparse
  16. sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
  17. from testing_support.auto_stub import TestCase
  18. import metrics
  19. # We have to disable monitoring before importing git_cl.
  20. metrics.DISABLE_METRICS_COLLECTION = True
  21. import git_cl
  22. import git_common
  23. import git_footers
  24. import subprocess2
  25. def callError(code=1, cmd='', cwd='', stdout='', stderr=''):
  26. return subprocess2.CalledProcessError(code, cmd, cwd, stdout, stderr)
  27. CERR1 = callError(1)
  28. def MakeNamedTemporaryFileMock(expected_content):
  29. class NamedTemporaryFileMock(object):
  30. def __init__(self, *args, **kwargs):
  31. self.name = '/tmp/named'
  32. self.expected_content = expected_content
  33. def __enter__(self):
  34. return self
  35. def __exit__(self, _type, _value, _tb):
  36. pass
  37. def write(self, content):
  38. if self.expected_content:
  39. assert content == self.expected_content
  40. def close(self):
  41. pass
  42. return NamedTemporaryFileMock
  43. class ChangelistMock(object):
  44. # A class variable so we can access it when we don't have access to the
  45. # instance that's being set.
  46. desc = ""
  47. def __init__(self, **kwargs):
  48. pass
  49. def GetIssue(self):
  50. return 1
  51. def GetDescription(self, force=False):
  52. return ChangelistMock.desc
  53. def UpdateDescription(self, desc, force=False):
  54. ChangelistMock.desc = desc
  55. class PresubmitMock(object):
  56. def __init__(self, *args, **kwargs):
  57. self.reviewers = []
  58. self.more_cc = ['chromium-reviews+test-more-cc@chromium.org']
  59. @staticmethod
  60. def should_continue():
  61. return True
  62. class RietveldMock(object):
  63. def __init__(self, *args, **kwargs):
  64. pass
  65. @staticmethod
  66. def get_description(issue, force=False):
  67. return 'Issue: %d' % issue
  68. @staticmethod
  69. def get_issue_properties(_issue, _messages):
  70. return {
  71. 'reviewers': ['joe@chromium.org', 'john@chromium.org'],
  72. 'messages': [
  73. {
  74. 'approval': True,
  75. 'disapproval': False,
  76. 'sender': 'john@chromium.org',
  77. },
  78. ],
  79. 'patchsets': [1, 20001],
  80. }
  81. @staticmethod
  82. def close_issue(_issue):
  83. return 'Closed'
  84. @staticmethod
  85. def get_patch(issue, patchset):
  86. return 'patch set from issue %s patchset %s' % (issue, patchset)
  87. @staticmethod
  88. def update_description(_issue, _description):
  89. return 'Updated'
  90. @staticmethod
  91. def add_comment(_issue, _comment):
  92. return 'Commented'
  93. class GitCheckoutMock(object):
  94. def __init__(self, *args, **kwargs):
  95. pass
  96. @staticmethod
  97. def reset():
  98. GitCheckoutMock.conflict = False
  99. def apply_patch(self, p):
  100. if GitCheckoutMock.conflict:
  101. raise Exception('failed')
  102. class WatchlistsMock(object):
  103. def __init__(self, _):
  104. pass
  105. @staticmethod
  106. def GetWatchersForPaths(_):
  107. return ['joe@example.com']
  108. class CodereviewSettingsFileMock(object):
  109. def __init__(self):
  110. pass
  111. # pylint: disable=no-self-use
  112. def read(self):
  113. return ("CODE_REVIEW_SERVER: gerrit.chromium.org\n" +
  114. "GERRIT_HOST: True\n")
  115. class AuthenticatorMock(object):
  116. def __init__(self, *_args):
  117. pass
  118. def has_cached_credentials(self):
  119. return True
  120. def authorize(self, http):
  121. return http
  122. def CookiesAuthenticatorMockFactory(hosts_with_creds=None, same_auth=False):
  123. """Use to mock Gerrit/Git credentials from ~/.netrc or ~/.gitcookies.
  124. Usage:
  125. >>> self.mock(git_cl.gerrit_util, "CookiesAuthenticator",
  126. CookiesAuthenticatorMockFactory({'host': ('user', _, 'pass')})
  127. OR
  128. >>> self.mock(git_cl.gerrit_util, "CookiesAuthenticator",
  129. CookiesAuthenticatorMockFactory(
  130. same_auth=('user', '', 'pass'))
  131. """
  132. class CookiesAuthenticatorMock(git_cl.gerrit_util.CookiesAuthenticator):
  133. def __init__(self): # pylint: disable=super-init-not-called
  134. # Intentionally not calling super() because it reads actual cookie files.
  135. pass
  136. @classmethod
  137. def get_gitcookies_path(cls):
  138. return '~/.gitcookies'
  139. @classmethod
  140. def get_netrc_path(cls):
  141. return '~/.netrc'
  142. def _get_auth_for_host(self, host):
  143. if same_auth:
  144. return same_auth
  145. return (hosts_with_creds or {}).get(host)
  146. return CookiesAuthenticatorMock
  147. class MockChangelistWithBranchAndIssue():
  148. def __init__(self, branch, issue):
  149. self.branch = branch
  150. self.issue = issue
  151. def GetBranch(self):
  152. return self.branch
  153. def GetIssue(self):
  154. return self.issue
  155. class SystemExitMock(Exception):
  156. pass
  157. class TestGitClBasic(unittest.TestCase):
  158. def test_get_description(self):
  159. cl = git_cl.Changelist(issue=1, codereview='rietveld',
  160. codereview_host='host')
  161. cl.description = 'x'
  162. cl.has_description = True
  163. cl._codereview_impl.FetchDescription = lambda *a, **kw: 'y'
  164. self.assertEquals(cl.GetDescription(), 'x')
  165. self.assertEquals(cl.GetDescription(force=True), 'y')
  166. self.assertEquals(cl.GetDescription(), 'y')
  167. def test_description_footers(self):
  168. cl = git_cl.Changelist(issue=1, codereview='gerrit',
  169. codereview_host='host')
  170. cl.description = '\n'.join([
  171. 'This is some message',
  172. '',
  173. 'It has some lines',
  174. 'and, also',
  175. '',
  176. 'Some: Really',
  177. 'Awesome: Footers',
  178. ])
  179. cl.has_description = True
  180. cl._codereview_impl.UpdateDescriptionRemote = lambda *a, **kw: 'y'
  181. msg, footers = cl.GetDescriptionFooters()
  182. self.assertEquals(
  183. msg, ['This is some message', '', 'It has some lines', 'and, also'])
  184. self.assertEquals(footers, [('Some', 'Really'), ('Awesome', 'Footers')])
  185. msg.append('wut')
  186. footers.append(('gnarly-dude', 'beans'))
  187. cl.UpdateDescriptionFooters(msg, footers)
  188. self.assertEquals(cl.GetDescription().splitlines(), [
  189. 'This is some message',
  190. '',
  191. 'It has some lines',
  192. 'and, also',
  193. 'wut'
  194. '',
  195. 'Some: Really',
  196. 'Awesome: Footers',
  197. 'Gnarly-Dude: beans',
  198. ])
  199. def test_get_bug_line_values(self):
  200. f = lambda p, bugs: list(git_cl._get_bug_line_values(p, bugs))
  201. self.assertEqual(f('', ''), [])
  202. self.assertEqual(f('', '123,v8:456'), ['123', 'v8:456'])
  203. self.assertEqual(f('v8', '456'), ['v8:456'])
  204. self.assertEqual(f('v8', 'chromium:123,456'), ['v8:456', 'chromium:123'])
  205. # Not nice, but not worth carying.
  206. self.assertEqual(f('v8', 'chromium:123,456,v8:123'),
  207. ['v8:456', 'chromium:123', 'v8:123'])
  208. def _test_git_number(self, parent_msg, dest_ref, child_msg,
  209. parent_hash='parenthash'):
  210. desc = git_cl.ChangeDescription(child_msg)
  211. desc.update_with_git_number_footers(parent_hash, parent_msg, dest_ref)
  212. return desc.description
  213. def assertEqualByLine(self, actual, expected):
  214. self.assertEqual(actual.splitlines(), expected.splitlines())
  215. def test_git_number_bad_parent(self):
  216. with self.assertRaises(ValueError):
  217. self._test_git_number('Parent', 'refs/heads/master', 'Child')
  218. def test_git_number_bad_parent_footer(self):
  219. with self.assertRaises(AssertionError):
  220. self._test_git_number(
  221. 'Parent\n'
  222. '\n'
  223. 'Cr-Commit-Position: wrong',
  224. 'refs/heads/master', 'Child')
  225. def test_git_number_bad_lineage_ignored(self):
  226. actual = self._test_git_number(
  227. 'Parent\n'
  228. '\n'
  229. 'Cr-Commit-Position: refs/heads/master@{#1}\n'
  230. 'Cr-Branched-From: mustBeReal40CharHash-branch@{#pos}',
  231. 'refs/heads/master', 'Child')
  232. self.assertEqualByLine(
  233. actual,
  234. 'Child\n'
  235. '\n'
  236. 'Cr-Commit-Position: refs/heads/master@{#2}\n'
  237. 'Cr-Branched-From: mustBeReal40CharHash-branch@{#pos}')
  238. def test_git_number_same_branch(self):
  239. actual = self._test_git_number(
  240. 'Parent\n'
  241. '\n'
  242. 'Cr-Commit-Position: refs/heads/master@{#12}',
  243. dest_ref='refs/heads/master',
  244. child_msg='Child')
  245. self.assertEqualByLine(
  246. actual,
  247. 'Child\n'
  248. '\n'
  249. 'Cr-Commit-Position: refs/heads/master@{#13}')
  250. def test_git_number_same_branch_mixed_footers(self):
  251. actual = self._test_git_number(
  252. 'Parent\n'
  253. '\n'
  254. 'Cr-Commit-Position: refs/heads/master@{#12}',
  255. dest_ref='refs/heads/master',
  256. child_msg='Child\n'
  257. '\n'
  258. 'Broken-by: design\n'
  259. 'BUG=123')
  260. self.assertEqualByLine(
  261. actual,
  262. 'Child\n'
  263. '\n'
  264. 'Broken-by: design\n'
  265. 'BUG=123\n'
  266. 'Cr-Commit-Position: refs/heads/master@{#13}')
  267. def test_git_number_same_branch_with_originals(self):
  268. actual = self._test_git_number(
  269. 'Parent\n'
  270. '\n'
  271. 'Cr-Commit-Position: refs/heads/master@{#12}',
  272. dest_ref='refs/heads/master',
  273. child_msg='Child\n'
  274. '\n'
  275. 'Some users are smart and insert their own footers\n'
  276. '\n'
  277. 'Cr-Whatever: value\n'
  278. 'Cr-Commit-Position: refs/copy/paste@{#22}')
  279. self.assertEqualByLine(
  280. actual,
  281. 'Child\n'
  282. '\n'
  283. 'Some users are smart and insert their own footers\n'
  284. '\n'
  285. 'Cr-Original-Whatever: value\n'
  286. 'Cr-Original-Commit-Position: refs/copy/paste@{#22}\n'
  287. 'Cr-Commit-Position: refs/heads/master@{#13}')
  288. def test_git_number_new_branch(self):
  289. actual = self._test_git_number(
  290. 'Parent\n'
  291. '\n'
  292. 'Cr-Commit-Position: refs/heads/master@{#12}',
  293. dest_ref='refs/heads/branch',
  294. child_msg='Child')
  295. self.assertEqualByLine(
  296. actual,
  297. 'Child\n'
  298. '\n'
  299. 'Cr-Commit-Position: refs/heads/branch@{#1}\n'
  300. 'Cr-Branched-From: parenthash-refs/heads/master@{#12}')
  301. def test_git_number_lineage(self):
  302. actual = self._test_git_number(
  303. 'Parent\n'
  304. '\n'
  305. 'Cr-Commit-Position: refs/heads/branch@{#1}\n'
  306. 'Cr-Branched-From: somehash-refs/heads/master@{#12}',
  307. dest_ref='refs/heads/branch',
  308. child_msg='Child')
  309. self.assertEqualByLine(
  310. actual,
  311. 'Child\n'
  312. '\n'
  313. 'Cr-Commit-Position: refs/heads/branch@{#2}\n'
  314. 'Cr-Branched-From: somehash-refs/heads/master@{#12}')
  315. def test_git_number_moooooooore_lineage(self):
  316. actual = self._test_git_number(
  317. 'Parent\n'
  318. '\n'
  319. 'Cr-Commit-Position: refs/heads/branch@{#5}\n'
  320. 'Cr-Branched-From: somehash-refs/heads/master@{#12}',
  321. dest_ref='refs/heads/mooore',
  322. child_msg='Child')
  323. self.assertEqualByLine(
  324. actual,
  325. 'Child\n'
  326. '\n'
  327. 'Cr-Commit-Position: refs/heads/mooore@{#1}\n'
  328. 'Cr-Branched-From: parenthash-refs/heads/branch@{#5}\n'
  329. 'Cr-Branched-From: somehash-refs/heads/master@{#12}')
  330. def test_git_number_ever_moooooooore_lineage(self):
  331. self.maxDiff = 10000 # pylint: disable=attribute-defined-outside-init
  332. actual = self._test_git_number(
  333. 'CQ commit on fresh new branch + numbering.\n'
  334. '\n'
  335. 'NOTRY=True\n'
  336. 'NOPRESUBMIT=True\n'
  337. 'BUG=\n'
  338. '\n'
  339. 'Review-Url: https://codereview.chromium.org/2577703003\n'
  340. 'Cr-Commit-Position: refs/heads/gnumb-test/br@{#1}\n'
  341. 'Cr-Branched-From: 0749ff9edc-refs/heads/gnumb-test/cq@{#4}\n'
  342. 'Cr-Branched-From: 5c49df2da6-refs/heads/master@{#41618}',
  343. dest_ref='refs/heads/gnumb-test/cl',
  344. child_msg='git cl on fresh new branch + numbering.\n'
  345. '\n'
  346. 'Review-Url: https://codereview.chromium.org/2575043003 .\n')
  347. self.assertEqualByLine(
  348. actual,
  349. 'git cl on fresh new branch + numbering.\n'
  350. '\n'
  351. 'Review-Url: https://codereview.chromium.org/2575043003 .\n'
  352. 'Cr-Commit-Position: refs/heads/gnumb-test/cl@{#1}\n'
  353. 'Cr-Branched-From: parenthash-refs/heads/gnumb-test/br@{#1}\n'
  354. 'Cr-Branched-From: 0749ff9edc-refs/heads/gnumb-test/cq@{#4}\n'
  355. 'Cr-Branched-From: 5c49df2da6-refs/heads/master@{#41618}')
  356. def test_git_number_cherry_pick(self):
  357. actual = self._test_git_number(
  358. 'Parent\n'
  359. '\n'
  360. 'Cr-Commit-Position: refs/heads/branch@{#1}\n'
  361. 'Cr-Branched-From: somehash-refs/heads/master@{#12}',
  362. dest_ref='refs/heads/branch',
  363. child_msg='Child, which is cherry-pick from master\n'
  364. '\n'
  365. 'Cr-Commit-Position: refs/heads/master@{#100}\n'
  366. '(cherry picked from commit deadbeef12345678deadbeef12345678deadbeef)')
  367. self.assertEqualByLine(
  368. actual,
  369. 'Child, which is cherry-pick from master\n'
  370. '\n'
  371. '(cherry picked from commit deadbeef12345678deadbeef12345678deadbeef)\n'
  372. '\n'
  373. 'Cr-Original-Commit-Position: refs/heads/master@{#100}\n'
  374. 'Cr-Commit-Position: refs/heads/branch@{#2}\n'
  375. 'Cr-Branched-From: somehash-refs/heads/master@{#12}')
  376. class TestParseIssueURL(unittest.TestCase):
  377. def _validate(self, parsed, issue=None, patchset=None, hostname=None,
  378. codereview=None, fail=False):
  379. self.assertIsNotNone(parsed)
  380. if fail:
  381. self.assertFalse(parsed.valid)
  382. return
  383. self.assertTrue(parsed.valid)
  384. self.assertEqual(parsed.issue, issue)
  385. self.assertEqual(parsed.patchset, patchset)
  386. self.assertEqual(parsed.hostname, hostname)
  387. self.assertEqual(parsed.codereview, codereview)
  388. def _run_and_validate(self, func, url, *args, **kwargs):
  389. result = func(urlparse.urlparse(url))
  390. if kwargs.pop('fail', False):
  391. self.assertIsNone(result)
  392. return None
  393. self._validate(result, *args, fail=False, **kwargs)
  394. def test_rietveld(self):
  395. def test(url, *args, **kwargs):
  396. self._run_and_validate(git_cl._RietveldChangelistImpl.ParseIssueURL, url,
  397. *args, codereview='rietveld', **kwargs)
  398. test('http://codereview.chromium.org/123',
  399. 123, None, 'codereview.chromium.org')
  400. test('https://codereview.chromium.org/123',
  401. 123, None, 'codereview.chromium.org')
  402. test('https://codereview.chromium.org/123/',
  403. 123, None, 'codereview.chromium.org')
  404. test('https://codereview.chromium.org/123/whatever',
  405. 123, None, 'codereview.chromium.org')
  406. test('https://codereview.chromium.org/123/#ps20001',
  407. 123, 20001, 'codereview.chromium.org')
  408. test('http://codereview.chromium.org/download/issue123_4.diff',
  409. 123, 4, 'codereview.chromium.org')
  410. # This looks like bad Gerrit, but is actually valid Rietveld, too.
  411. test('https://chrome-review.source.com/123/4/',
  412. 123, None, 'chrome-review.source.com')
  413. test('https://codereview.chromium.org/deadbeaf', fail=True)
  414. test('https://codereview.chromium.org/api/123', fail=True)
  415. test('bad://codereview.chromium.org/123', fail=True)
  416. test('http://codereview.chromium.org/download/issue123_4.diffff', fail=True)
  417. def test_gerrit(self):
  418. def test(url, issue=None, patchset=None, hostname=None, fail=None):
  419. self._test_ParseIssueUrl(
  420. git_cl._GerritChangelistImpl.ParseIssueURL,
  421. url, issue, patchset, hostname, fail)
  422. def test(url, *args, **kwargs):
  423. self._run_and_validate(git_cl._GerritChangelistImpl.ParseIssueURL, url,
  424. *args, codereview='gerrit', **kwargs)
  425. test('http://chrome-review.source.com/c/123',
  426. 123, None, 'chrome-review.source.com')
  427. test('https://chrome-review.source.com/c/123/',
  428. 123, None, 'chrome-review.source.com')
  429. test('https://chrome-review.source.com/c/123/4',
  430. 123, 4, 'chrome-review.source.com')
  431. test('https://chrome-review.source.com/#/c/123/4',
  432. 123, 4, 'chrome-review.source.com')
  433. test('https://chrome-review.source.com/c/123/4',
  434. 123, 4, 'chrome-review.source.com')
  435. test('https://chrome-review.source.com/123',
  436. 123, None, 'chrome-review.source.com')
  437. test('https://chrome-review.source.com/123/4',
  438. 123, 4, 'chrome-review.source.com')
  439. test('https://chrome-review.source.com/c/123/1/whatisthis', fail=True)
  440. test('https://chrome-review.source.com/c/abc/', fail=True)
  441. test('ssh://chrome-review.source.com/c/123/1/', fail=True)
  442. def test_ParseIssueNumberArgument(self):
  443. def test(arg, *args, **kwargs):
  444. codereview_hint = kwargs.pop('hint', None)
  445. self._validate(git_cl.ParseIssueNumberArgument(arg, codereview_hint),
  446. *args, **kwargs)
  447. test('123', 123)
  448. test('', fail=True)
  449. test('abc', fail=True)
  450. test('123/1', fail=True)
  451. test('123a', fail=True)
  452. test('ssh://chrome-review.source.com/#/c/123/4/', fail=True)
  453. # Rietveld.
  454. test('https://codereview.source.com/www123', fail=True)
  455. # Matches both, but we take Rietveld now by default.
  456. test('https://codereview.source.com/123',
  457. 123, None, 'codereview.source.com', 'rietveld')
  458. # Matches both, but we take Gerrit if specifically requested.
  459. test('https://codereview.source.com/123',
  460. 123, None, 'codereview.source.com', 'gerrit',
  461. hint='gerrit')
  462. # Gerrrit.
  463. test('https://chrome-review.source.com/c/123/4',
  464. 123, 4, 'chrome-review.source.com', 'gerrit')
  465. test('https://chrome-review.source.com/bad/123/4', fail=True)
  466. class GitCookiesCheckerTest(TestCase):
  467. def setUp(self):
  468. super(GitCookiesCheckerTest, self).setUp()
  469. self.c = git_cl._GitCookiesChecker()
  470. self.c._all_hosts = []
  471. def mock_hosts_creds(self, subhost_identity_pairs):
  472. def ensure_googlesource(h):
  473. if not h.endswith(self.c._GOOGLESOURCE):
  474. assert not h.endswith('.')
  475. return h + '.' + self.c._GOOGLESOURCE
  476. return h
  477. self.c._all_hosts = [(ensure_googlesource(h), i, '.gitcookies')
  478. for h, i in subhost_identity_pairs]
  479. def test_identity_parsing(self):
  480. self.assertEqual(self.c._parse_identity('ldap.google.com'),
  481. ('ldap', 'google.com'))
  482. self.assertEqual(self.c._parse_identity('git-ldap.example.com'),
  483. ('ldap', 'example.com'))
  484. # Specical case because we know there are no subdomains in chromium.org.
  485. self.assertEqual(self.c._parse_identity('git-note.period.chromium.org'),
  486. ('note.period', 'chromium.org'))
  487. # Pathological: ".period." can be either username OR domain, more likely
  488. # domain.
  489. self.assertEqual(self.c._parse_identity('git-note.period.example.com'),
  490. ('note', 'period.example.com'))
  491. def test_analysis_nothing(self):
  492. self.c._all_hosts = []
  493. self.assertFalse(self.c.has_generic_host())
  494. self.assertEqual(set(), self.c.get_conflicting_hosts())
  495. self.assertEqual(set(), self.c.get_duplicated_hosts())
  496. self.assertEqual(set(), self.c.get_partially_configured_hosts())
  497. self.assertEqual(set(), self.c.get_hosts_with_wrong_identities())
  498. def test_analysis(self):
  499. self.mock_hosts_creds([
  500. ('.googlesource.com', 'git-example.chromium.org'),
  501. ('chromium', 'git-example.google.com'),
  502. ('chromium-review', 'git-example.google.com'),
  503. ('chrome-internal', 'git-example.chromium.org'),
  504. ('chrome-internal-review', 'git-example.chromium.org'),
  505. ('conflict', 'git-example.google.com'),
  506. ('conflict-review', 'git-example.chromium.org'),
  507. ('dup', 'git-example.google.com'),
  508. ('dup', 'git-example.google.com'),
  509. ('dup-review', 'git-example.google.com'),
  510. ('partial', 'git-example.google.com'),
  511. ('gpartial-review', 'git-example.google.com'),
  512. ])
  513. self.assertTrue(self.c.has_generic_host())
  514. self.assertEqual(set(['conflict.googlesource.com']),
  515. self.c.get_conflicting_hosts())
  516. self.assertEqual(set(['dup.googlesource.com']),
  517. self.c.get_duplicated_hosts())
  518. self.assertEqual(set(['partial.googlesource.com',
  519. 'gpartial-review.googlesource.com']),
  520. self.c.get_partially_configured_hosts())
  521. self.assertEqual(set(['chromium.googlesource.com',
  522. 'chrome-internal.googlesource.com']),
  523. self.c.get_hosts_with_wrong_identities())
  524. def test_report_no_problems(self):
  525. self.test_analysis_nothing()
  526. self.mock(sys, 'stdout', StringIO.StringIO())
  527. self.assertFalse(self.c.find_and_report_problems())
  528. self.assertEqual(sys.stdout.getvalue(), '')
  529. def test_report(self):
  530. self.test_analysis()
  531. self.mock(sys, 'stdout', StringIO.StringIO())
  532. self.mock(git_cl.gerrit_util.CookiesAuthenticator, 'get_gitcookies_path',
  533. classmethod(lambda _: '~/.gitcookies'))
  534. self.assertTrue(self.c.find_and_report_problems())
  535. with open(os.path.join(os.path.dirname(__file__),
  536. 'git_cl_creds_check_report.txt')) as f:
  537. expected = f.read()
  538. def by_line(text):
  539. return [l.rstrip() for l in text.rstrip().splitlines()]
  540. self.maxDiff = 10000 # pylint: disable=attribute-defined-outside-init
  541. self.assertEqual(by_line(sys.stdout.getvalue().strip()), by_line(expected))
  542. class TestGitCl(TestCase):
  543. def setUp(self):
  544. super(TestGitCl, self).setUp()
  545. self.calls = []
  546. self._calls_done = []
  547. self.mock(subprocess2, 'call', self._mocked_call)
  548. self.mock(subprocess2, 'check_call', self._mocked_call)
  549. self.mock(subprocess2, 'check_output', self._mocked_call)
  550. self.mock(subprocess2, 'communicate',
  551. lambda *a, **kw: ([self._mocked_call(*a, **kw), ''], 0))
  552. self.mock(git_cl.gclient_utils, 'CheckCallAndFilter', self._mocked_call)
  553. self.mock(git_common, 'is_dirty_git_tree', lambda x: False)
  554. self.mock(git_common, 'get_or_create_merge_base',
  555. lambda *a: (
  556. self._mocked_call(['get_or_create_merge_base']+list(a))))
  557. self.mock(git_cl, 'BranchExists', lambda _: True)
  558. self.mock(git_cl, 'FindCodereviewSettingsFile', lambda: '')
  559. self.mock(git_cl, 'ask_for_data', lambda *a, **k: self._mocked_call(
  560. *(['ask_for_data'] + list(a)), **k))
  561. self.mock(git_cl, 'write_json', lambda path, contents:
  562. self._mocked_call('write_json', path, contents))
  563. self.mock(git_cl.presubmit_support, 'DoPresubmitChecks', PresubmitMock)
  564. self.mock(git_cl.rietveld, 'Rietveld', RietveldMock)
  565. self.mock(git_cl.rietveld, 'CachingRietveld', RietveldMock)
  566. self.mock(git_cl.checkout, 'GitCheckout', GitCheckoutMock)
  567. GitCheckoutMock.reset()
  568. self.mock(git_cl.upload, 'RealMain', self.fail)
  569. self.mock(git_cl.watchlists, 'Watchlists', WatchlistsMock)
  570. self.mock(git_cl.auth, 'get_authenticator_for_host', AuthenticatorMock)
  571. self.mock(git_cl.gerrit_util, 'GetChangeDetail',
  572. lambda *args, **kwargs: self._mocked_call(
  573. 'GetChangeDetail', *args, **kwargs))
  574. self.mock(git_cl.gerrit_util, 'GetChangeComments',
  575. lambda *args, **kwargs: self._mocked_call(
  576. 'GetChangeComments', *args, **kwargs))
  577. self.mock(git_cl.gerrit_util, 'AddReviewers',
  578. lambda h, i, reviewers, ccs, notify: self._mocked_call(
  579. 'AddReviewers', h, i, reviewers, ccs, notify))
  580. self.mock(git_cl.gerrit_util, 'SetReview',
  581. lambda h, i, msg=None, labels=None, notify=None:
  582. self._mocked_call('SetReview', h, i, msg, labels, notify))
  583. self.mock(git_cl.gerrit_util.LuciContextAuthenticator, 'is_luci',
  584. staticmethod(lambda: False))
  585. self.mock(git_cl.gerrit_util.GceAuthenticator, 'is_gce',
  586. classmethod(lambda _: False))
  587. self.mock(git_cl, 'DieWithError',
  588. lambda msg, change=None: self._mocked_call(['DieWithError', msg]))
  589. # It's important to reset settings to not have inter-tests interference.
  590. git_cl.settings = None
  591. def tearDown(self):
  592. try:
  593. self.assertEquals([], self.calls)
  594. except AssertionError:
  595. if not self.has_failed():
  596. raise
  597. # Sadly, has_failed() returns True if this OR any other tests before this
  598. # one have failed.
  599. git_cl.logging.error(
  600. '!!!!!! IF YOU SEE THIS, READ BELOW, IT WILL SAVE YOUR TIME !!!!!\n'
  601. 'There are un-consumed self.calls after this test has finished.\n'
  602. 'If you don\'t know which test this is, run:\n'
  603. ' tests/git_cl_tests.py -v\n'
  604. 'If you are already running only this test, then **first** fix the '
  605. 'problem whose exception is emitted below by unittest runner.\n'
  606. 'Else, to be sure what\'s going on, run this test **alone** with \n'
  607. ' tests/git_cl_tests.py TestGitCl.<name>\n'
  608. 'and follow instructions above.\n' +
  609. '=' * 80)
  610. finally:
  611. super(TestGitCl, self).tearDown()
  612. def _mocked_call(self, *args, **_kwargs):
  613. self.assertTrue(
  614. self.calls,
  615. '@%d Expected: <Missing> Actual: %r' % (len(self._calls_done), args))
  616. top = self.calls.pop(0)
  617. expected_args, result = top
  618. # Also logs otherwise it could get caught in a try/finally and be hard to
  619. # diagnose.
  620. if expected_args != args:
  621. N = 5
  622. prior_calls = '\n '.join(
  623. '@%d: %r' % (len(self._calls_done) - N + i, c[0])
  624. for i, c in enumerate(self._calls_done[-N:]))
  625. following_calls = '\n '.join(
  626. '@%d: %r' % (len(self._calls_done) + i + 1, c[0])
  627. for i, c in enumerate(self.calls[:N]))
  628. extended_msg = (
  629. 'A few prior calls:\n %s\n\n'
  630. 'This (expected):\n @%d: %r\n'
  631. 'This (actual):\n @%d: %r\n\n'
  632. 'A few following expected calls:\n %s' %
  633. (prior_calls, len(self._calls_done), expected_args,
  634. len(self._calls_done), args, following_calls))
  635. git_cl.logging.error(extended_msg)
  636. self.fail('@%d\n'
  637. ' Expected: %r\n'
  638. ' Actual: %r' % (
  639. len(self._calls_done), expected_args, args))
  640. self._calls_done.append(top)
  641. if isinstance(result, Exception):
  642. raise result
  643. return result
  644. def test_ask_for_explicit_yes_true(self):
  645. self.calls = [
  646. (('ask_for_data', 'prompt [Yes/No]: '), 'blah'),
  647. (('ask_for_data', 'Please, type yes or no: '), 'ye'),
  648. ]
  649. self.assertTrue(git_cl.ask_for_explicit_yes('prompt'))
  650. def test_LoadCodereviewSettingsFromFile_gerrit(self):
  651. codereview_file = StringIO.StringIO('GERRIT_HOST: true')
  652. self.calls = [
  653. ((['git', 'config', '--unset-all', 'rietveld.cc'],), CERR1),
  654. ((['git', 'config', '--unset-all', 'rietveld.private'],), CERR1),
  655. ((['git', 'config', '--unset-all', 'rietveld.tree-status-url'],), CERR1),
  656. ((['git', 'config', '--unset-all', 'rietveld.viewvc-url'],), CERR1),
  657. ((['git', 'config', '--unset-all', 'rietveld.bug-prefix'],), CERR1),
  658. ((['git', 'config', '--unset-all', 'rietveld.cpplint-regex'],), CERR1),
  659. ((['git', 'config', '--unset-all', 'rietveld.cpplint-ignore-regex'],),
  660. CERR1),
  661. ((['git', 'config', '--unset-all', 'rietveld.project'],), CERR1),
  662. ((['git', 'config', '--unset-all', 'rietveld.run-post-upload-hook'],),
  663. CERR1),
  664. ((['git', 'config', 'gerrit.host', 'true'],), ''),
  665. ]
  666. self.assertIsNone(git_cl.LoadCodereviewSettingsFromFile(codereview_file))
  667. @classmethod
  668. def _is_gerrit_calls(cls, gerrit=False):
  669. return [((['git', 'config', 'rietveld.autoupdate'],), ''),
  670. ((['git', 'config', 'gerrit.host'],), 'True' if gerrit else '')]
  671. @classmethod
  672. def _upload_calls(cls, private):
  673. return (cls._rietveld_git_base_calls() +
  674. cls._git_upload_calls(private))
  675. @classmethod
  676. def _rietveld_upload_no_rev_calls(cls):
  677. return (cls._rietveld_git_base_calls() + [
  678. ((['git', 'config', 'core.editor'],), ''),
  679. ])
  680. @classmethod
  681. def _rietveld_git_base_calls(cls):
  682. stat_call = ((['git', 'diff', '--no-ext-diff', '--stat',
  683. '-l100000', '-C50', 'fake_ancestor_sha', 'HEAD'],), '+dat')
  684. return cls._is_gerrit_calls() + [
  685. ((['git', 'symbolic-ref', 'HEAD'],), 'master'),
  686. ((['git', 'config', 'branch.master.rietveldissue'],), CERR1),
  687. ((['git', 'config', 'branch.master.gerritissue'],), CERR1),
  688. ((['git', 'config', 'rietveld.server'],),
  689. 'codereview.example.com'),
  690. ((['git', 'config', 'branch.master.merge'],), 'master'),
  691. ((['git', 'config', 'branch.master.remote'],), 'origin'),
  692. ((['get_or_create_merge_base', 'master', 'master'],),
  693. 'fake_ancestor_sha'),
  694. ] + cls._git_sanity_checks('fake_ancestor_sha', 'master') + [
  695. ((['git', 'rev-parse', '--show-cdup'],), ''),
  696. ((['git', 'rev-parse', 'HEAD'],), '12345'),
  697. ((['git', '-c', 'core.quotePath=false', 'diff',
  698. '--name-status', '--no-renames', '-r', 'fake_ancestor_sha...', '.'],),
  699. 'M\t.gitignore\n'),
  700. ((['git', 'config', 'branch.master.rietveldpatchset'],), CERR1),
  701. ((['git', 'log', '--pretty=format:%s%n%n%b',
  702. 'fake_ancestor_sha...'],),
  703. 'foo'),
  704. ((['git', 'config', 'user.email'],), 'me@example.com'),
  705. stat_call,
  706. ((['git', 'log', '--pretty=format:%s\n\n%b',
  707. 'fake_ancestor_sha..HEAD'],),
  708. 'desc\n'),
  709. ((['git', 'config', 'rietveld.bug-prefix'],), ''),
  710. ]
  711. @classmethod
  712. def _git_upload_calls(cls, private):
  713. if private:
  714. cc_call = []
  715. private_call = []
  716. else:
  717. cc_call = [((['git', 'config', 'rietveld.cc'],), '')]
  718. private_call = [
  719. ((['git', 'config', 'rietveld.private'],), '')]
  720. return [
  721. ((['git', 'config', 'core.editor'],), ''),
  722. ] + cc_call + private_call + [
  723. ((['git', 'config', 'branch.master.base-url'],), ''),
  724. ((['git', 'config', 'remote.origin.url'],), ''),
  725. ((['git', 'config', 'rietveld.project'],), ''),
  726. ((['git', 'config', 'branch.master.rietveldissue', '1'],), ''),
  727. ((['git', 'config', 'branch.master.rietveldserver',
  728. 'https://codereview.example.com'],), ''),
  729. ((['git',
  730. 'config', 'branch.master.rietveldpatchset', '2'],), ''),
  731. ] + cls._git_post_upload_calls()
  732. @classmethod
  733. def _git_post_upload_calls(cls):
  734. return [
  735. ((['git', 'rev-parse', 'HEAD'],), 'hash'),
  736. ((['git', 'symbolic-ref', 'HEAD'],), 'hash'),
  737. ((['git',
  738. 'config', 'branch.hash.last-upload-hash', 'hash'],), ''),
  739. ((['git', 'config', 'rietveld.run-post-upload-hook'],), ''),
  740. ]
  741. @staticmethod
  742. def _git_sanity_checks(diff_base, working_branch, get_remote_branch=True):
  743. fake_ancestor = 'fake_ancestor'
  744. fake_cl = 'fake_cl_for_patch'
  745. return [
  746. ((['git',
  747. 'rev-parse', '--verify', diff_base],), fake_ancestor),
  748. ((['git',
  749. 'merge-base', fake_ancestor, 'HEAD'],), fake_ancestor),
  750. ((['git',
  751. 'rev-list', '^' + fake_ancestor, 'HEAD'],), fake_cl),
  752. # Mock a config miss (error code 1)
  753. ((['git',
  754. 'config', 'gitcl.remotebranch'],), CERR1),
  755. ] + ([
  756. # Call to GetRemoteBranch()
  757. ((['git',
  758. 'config', 'branch.%s.merge' % working_branch],),
  759. 'refs/heads/master'),
  760. ((['git',
  761. 'config', 'branch.%s.remote' % working_branch],), 'origin'),
  762. ] if get_remote_branch else []) + [
  763. ((['git', 'rev-list', '^' + fake_ancestor,
  764. 'refs/remotes/origin/master'],), ''),
  765. ]
  766. @staticmethod
  767. def _cmd_line(description, args, private, cc):
  768. """Returns the upload command line passed to upload.RealMain()."""
  769. return [
  770. 'upload', '--assume_yes', '--server', 'https://codereview.example.com',
  771. '--message', description
  772. ] + args + [
  773. '--cc',
  774. ','.join(
  775. ['joe@example.com', 'chromium-reviews+test-more-cc@chromium.org'] +
  776. cc),
  777. ] + (['--private'] if private else []) + ['fake_ancestor_sha', 'HEAD']
  778. def _run_reviewer_test(
  779. self,
  780. upload_args,
  781. expected_description,
  782. returned_description,
  783. final_description,
  784. reviewers,
  785. private=False,
  786. cc=None):
  787. """Generic reviewer test framework."""
  788. self.mock(git_cl.sys, 'stdout', StringIO.StringIO())
  789. private = '--private' in upload_args
  790. cc = cc or []
  791. self.calls = self._upload_calls(private)
  792. def RunEditor(desc, _, **kwargs):
  793. self.assertEquals(
  794. '# Enter a description of the change.\n'
  795. '# This will be displayed on the codereview site.\n'
  796. '# The first line will also be used as the subject of the review.\n'
  797. '#--------------------This line is 72 characters long'
  798. '--------------------\n' +
  799. expected_description,
  800. desc)
  801. return returned_description
  802. self.mock(git_cl.gclient_utils, 'RunEditor', RunEditor)
  803. def check_upload(args):
  804. cmd_line = self._cmd_line(final_description, reviewers, private, cc)
  805. self.assertEquals(cmd_line, args)
  806. return 1, 2
  807. self.mock(git_cl.upload, 'RealMain', check_upload)
  808. git_cl.main(['upload'] + upload_args)
  809. def test_no_reviewer(self):
  810. self._run_reviewer_test(
  811. [],
  812. 'desc\n\nBUG=',
  813. '# Blah blah comment.\ndesc\n\nBUG=',
  814. 'desc\n\nBUG=',
  815. [])
  816. def test_private(self):
  817. self._run_reviewer_test(
  818. ['--private'],
  819. 'desc\n\nBUG=',
  820. '# Blah blah comment.\ndesc\n\nBUG=\n',
  821. 'desc\n\nBUG=',
  822. [])
  823. def test_reviewers_cmd_line(self):
  824. # Reviewer is passed as-is
  825. description = 'desc\n\nR=foo@example.com\nBUG='
  826. self._run_reviewer_test(
  827. ['-r' 'foo@example.com'],
  828. description,
  829. '\n%s\n' % description,
  830. description,
  831. ['--reviewers=foo@example.com'])
  832. def test_reviewer_tbr_overriden(self):
  833. # Reviewer is overriden with TBR
  834. # Also verifies the regexp work without a trailing LF
  835. description = 'Foo Bar\n\nTBR=reviewer@example.com'
  836. self._run_reviewer_test(
  837. ['-r' 'foo@example.com'],
  838. 'desc\n\nR=foo@example.com\nBUG=',
  839. description.strip('\n'),
  840. description,
  841. ['--reviewers=reviewer@example.com'])
  842. def test_reviewer_multiple(self):
  843. # Handles multiple R= or TBR= lines.
  844. description = (
  845. 'Foo Bar\nTBR=reviewer@example.com\nBUG=\nR=another@example.com\n'
  846. 'CC=more@example.com,people@example.com')
  847. self._run_reviewer_test(
  848. [],
  849. 'desc\n\nBUG=',
  850. description,
  851. description,
  852. ['--reviewers=another@example.com,reviewer@example.com'],
  853. cc=['more@example.com', 'people@example.com'])
  854. def test_reviewer_send_mail(self):
  855. # --send-mail can be used without -r if R= is used
  856. description = 'Foo Bar\nR=reviewer@example.com'
  857. self._run_reviewer_test(
  858. ['--send-mail'],
  859. 'desc\n\nBUG=',
  860. description.strip('\n'),
  861. description,
  862. ['--reviewers=reviewer@example.com', '--send_mail'])
  863. def test_reviewer_send_mail_no_rev(self):
  864. # Fails without a reviewer.
  865. stdout = StringIO.StringIO()
  866. self.calls = self._rietveld_upload_no_rev_calls() + [
  867. ((['DieWithError', 'Must specify reviewers to send email.'],),
  868. SystemExitMock())
  869. ]
  870. def RunEditor(desc, _, **kwargs):
  871. return desc
  872. self.mock(git_cl.gclient_utils, 'RunEditor', RunEditor)
  873. self.mock(sys, 'stdout', stdout)
  874. with self.assertRaises(SystemExitMock):
  875. git_cl.main(['upload', '--send-mail'])
  876. self.assertEqual(
  877. '=====================================\n'
  878. 'NOTICE: Rietveld is being deprecated. '
  879. 'You can upload changes to Gerrit with\n'
  880. ' git cl upload --gerrit\n'
  881. 'or set Gerrit to be your default code review tool with\n'
  882. ' git config gerrit.host true\n'
  883. '=====================================\n',
  884. stdout.getvalue())
  885. def test_bug_on_cmd(self):
  886. self._run_reviewer_test(
  887. ['--bug=500658,proj:123'],
  888. 'desc\n\nBUG=500658\nBUG=proj:123',
  889. '# Blah blah comment.\ndesc\n\nBUG=500658\nBUG=proj:1234',
  890. 'desc\n\nBUG=500658\nBUG=proj:1234',
  891. [])
  892. def test_git_numberer_not_whitelisted_repo(self):
  893. self.calls = []
  894. res = git_cl._is_git_numberer_enabled(
  895. remote_url='https://chromium.googlesource.com/chromium/tools/build',
  896. remote_ref='refs/whatever')
  897. self.assertEqual(res, False)
  898. def test_git_numberer_fail_fetch(self):
  899. self.mock(git_cl.sys, 'stdout', StringIO.StringIO())
  900. self.calls = [
  901. ((['git', 'fetch', 'https://chromium.googlesource.com/chromium/src',
  902. '+refs/meta/config:refs/git_cl/meta/config'],), CERR1),
  903. ]
  904. res = git_cl._is_git_numberer_enabled(
  905. remote_url='https://chromium.googlesource.com/chromium/src',
  906. remote_ref='refs/whatever')
  907. self.assertEqual(res, False)
  908. def test_git_numberer_valid_configs(self):
  909. with git_cl.gclient_utils.temporary_directory() as tempdir:
  910. @contextlib.contextmanager
  911. def fake_temporary_directory(**kwargs):
  912. yield tempdir
  913. self.mock(git_cl.gclient_utils, 'temporary_directory',
  914. fake_temporary_directory)
  915. self._test_GitNumbererState_valid_configs_inner(tempdir)
  916. def _test_GitNumbererState_valid_configs_inner(self, tempdir):
  917. self.calls = [
  918. ((['git', 'fetch', 'https://chromium.googlesource.com/chromium/src',
  919. '+refs/meta/config:refs/git_cl/meta/config'],), ''),
  920. ((['git', 'show', 'refs/git_cl/meta/config:project.config'],),
  921. '''
  922. [plugin "git-numberer"]
  923. validate-enabled-refglob = refs/else/*
  924. validate-enabled-refglob = refs/heads/*
  925. validate-disabled-refglob = refs/heads/disabled
  926. validate-disabled-refglob = refs/branch-heads/*
  927. '''),
  928. ((['git', 'config', '-f', os.path.join(tempdir, 'project.config') ,
  929. '--get-all', 'plugin.git-numberer.validate-enabled-refglob'],),
  930. 'refs/else/*\n'
  931. 'refs/heads/*\n'),
  932. ((['git', 'config', '-f', os.path.join(tempdir, 'project.config') ,
  933. '--get-all', 'plugin.git-numberer.validate-disabled-refglob'],),
  934. 'refs/heads/disabled\n'
  935. 'refs/branch-heads/*\n'),
  936. ] * 3 # 3 tests below have exactly the same IO.
  937. res = git_cl._is_git_numberer_enabled(
  938. remote_url='https://chromium.googlesource.com/chromium/src',
  939. remote_ref='refs/heads/test')
  940. self.assertEqual(res, True)
  941. res = git_cl._is_git_numberer_enabled(
  942. remote_url='https://chromium.googlesource.com/chromium/src',
  943. remote_ref='refs/heads/disabled')
  944. self.assertEqual(res, False)
  945. # Validator is disabled by default, even if it's not explicitly in disabled
  946. # refglobs.
  947. res = git_cl._is_git_numberer_enabled(
  948. remote_url='https://chromium.googlesource.com/chromium/src',
  949. remote_ref='refs/arbitrary/ref')
  950. self.assertEqual(res, False)
  951. @classmethod
  952. def _gerrit_ensure_auth_calls(cls, issue=None, skip_auth_check=False):
  953. cmd = ['git', 'config', '--bool', 'gerrit.skip-ensure-authenticated']
  954. if skip_auth_check:
  955. return [((cmd, ), 'true')]
  956. calls = [((cmd, ), CERR1)]
  957. if issue:
  958. calls.extend([
  959. ((['git', 'config', 'branch.master.gerritserver'],), CERR1),
  960. ])
  961. calls.extend([
  962. ((['git', 'config', 'branch.master.merge'],), 'refs/heads/master'),
  963. ((['git', 'config', 'branch.master.remote'],), 'origin'),
  964. ((['git', 'config', 'remote.origin.url'],),
  965. 'https://chromium.googlesource.com/my/repo'),
  966. ((['git', 'config', 'remote.origin.url'],),
  967. 'https://chromium.googlesource.com/my/repo'),
  968. ])
  969. return calls
  970. @classmethod
  971. def _gerrit_base_calls(cls, issue=None, fetched_description=None,
  972. fetched_status=None, other_cl_owner=None,
  973. custom_cl_base=None):
  974. calls = cls._is_gerrit_calls(True)
  975. calls += [
  976. ((['git', 'symbolic-ref', 'HEAD'],), 'master'),
  977. ((['git', 'config', 'branch.master.rietveldissue'],), CERR1),
  978. ((['git', 'config', 'branch.master.gerritissue'],),
  979. CERR1 if issue is None else str(issue)),
  980. ]
  981. if custom_cl_base:
  982. ancestor_revision = custom_cl_base
  983. else:
  984. # Determine ancestor_revision to be merge base.
  985. ancestor_revision = 'fake_ancestor_sha'
  986. calls += [
  987. ((['git', 'config', 'branch.master.merge'],), 'refs/heads/master'),
  988. ((['git', 'config', 'branch.master.remote'],), 'origin'),
  989. ((['get_or_create_merge_base', 'master',
  990. 'refs/remotes/origin/master'],), ancestor_revision),
  991. ]
  992. # Calls to verify branch point is ancestor
  993. calls += cls._gerrit_ensure_auth_calls(issue=issue)
  994. if issue:
  995. calls += [
  996. (('GetChangeDetail', 'chromium-review.googlesource.com',
  997. '123456',
  998. ['DETAILED_ACCOUNTS', 'CURRENT_REVISION', 'CURRENT_COMMIT']),
  999. {
  1000. 'owner': {'email': (other_cl_owner or 'owner@example.com')},
  1001. 'change_id': '123456789',
  1002. 'current_revision': 'sha1_of_current_revision',
  1003. 'revisions': { 'sha1_of_current_revision': {
  1004. 'commit': {'message': fetched_description},
  1005. }},
  1006. 'status': fetched_status or 'NEW',
  1007. }),
  1008. ]
  1009. if fetched_status == 'ABANDONED':
  1010. calls += [
  1011. (('DieWithError', 'Change https://chromium-review.googlesource.com/'
  1012. '123456 has been abandoned, new uploads are not '
  1013. 'allowed'), SystemExitMock()),
  1014. ]
  1015. return calls
  1016. if other_cl_owner:
  1017. calls += [
  1018. (('ask_for_data', 'Press Enter to upload, or Ctrl+C to abort'), ''),
  1019. ]
  1020. calls += cls._git_sanity_checks(ancestor_revision, 'master',
  1021. get_remote_branch=False)
  1022. calls += [
  1023. ((['git', 'rev-parse', '--show-cdup'],), ''),
  1024. ((['git', 'rev-parse', 'HEAD'],), '12345'),
  1025. ((['git', '-c', 'core.quotePath=false', 'diff', '--name-status',
  1026. '--no-renames', '-r', ancestor_revision + '...', '.'],),
  1027. 'M\t.gitignore\n'),
  1028. ((['git', 'config', 'branch.master.gerritpatchset'],), CERR1),
  1029. ]
  1030. if not issue:
  1031. calls += [
  1032. ((['git', 'log', '--pretty=format:%s%n%n%b',
  1033. ancestor_revision + '...'],),
  1034. 'foo'),
  1035. ]
  1036. calls += [
  1037. ((['git', 'config', 'user.email'],), 'me@example.com'),
  1038. ((['git', 'diff', '--no-ext-diff', '--stat', '-l100000', '-C50'] +
  1039. ([custom_cl_base] if custom_cl_base else
  1040. [ancestor_revision, 'HEAD']),),
  1041. '+dat'),
  1042. ]
  1043. return calls
  1044. @classmethod
  1045. def _gerrit_upload_calls(cls, description, reviewers, squash,
  1046. squash_mode='default',
  1047. expected_upstream_ref='origin/refs/heads/master',
  1048. title=None, notify=False,
  1049. post_amend_description=None, issue=None, cc=None,
  1050. git_mirror=None, custom_cl_base=None, tbr=None):
  1051. if post_amend_description is None:
  1052. post_amend_description = description
  1053. cc = cc or []
  1054. # Determined in `_gerrit_base_calls`.
  1055. determined_ancestor_revision = custom_cl_base or 'fake_ancestor_sha'
  1056. calls = []
  1057. if squash_mode == 'default':
  1058. calls.extend([
  1059. ((['git', 'config', '--bool', 'gerrit.override-squash-uploads'],), ''),
  1060. ((['git', 'config', '--bool', 'gerrit.squash-uploads'],), ''),
  1061. ])
  1062. elif squash_mode in ('override_squash', 'override_nosquash'):
  1063. calls.extend([
  1064. ((['git', 'config', '--bool', 'gerrit.override-squash-uploads'],),
  1065. 'true' if squash_mode == 'override_squash' else 'false'),
  1066. ])
  1067. else:
  1068. assert squash_mode in ('squash', 'nosquash')
  1069. # If issue is given, then description is fetched from Gerrit instead.
  1070. if issue is None:
  1071. calls += [
  1072. ((['git', 'log', '--pretty=format:%s\n\n%b',
  1073. ((custom_cl_base + '..') if custom_cl_base else
  1074. 'fake_ancestor_sha..HEAD')],),
  1075. description),
  1076. ]
  1077. if squash:
  1078. title = 'Initial_upload'
  1079. else:
  1080. if not title:
  1081. calls += [
  1082. ((['git', 'show', '-s', '--format=%s', 'HEAD'],), ''),
  1083. (('ask_for_data', 'Title for patchset []: '), 'User input'),
  1084. ]
  1085. title = 'User_input'
  1086. if not git_footers.get_footer_change_id(description) and not squash:
  1087. calls += [
  1088. (('DownloadGerritHook', False), ''),
  1089. # Amending of commit message to get the Change-Id.
  1090. ((['git', 'log', '--pretty=format:%s\n\n%b',
  1091. determined_ancestor_revision + '..HEAD'],),
  1092. description),
  1093. ((['git', 'commit', '--amend', '-m', description],), ''),
  1094. ((['git', 'log', '--pretty=format:%s\n\n%b',
  1095. determined_ancestor_revision + '..HEAD'],),
  1096. post_amend_description)
  1097. ]
  1098. if squash:
  1099. if not issue:
  1100. # Prompting to edit description on first upload.
  1101. calls += [
  1102. ((['git', 'config', 'core.editor'],), ''),
  1103. ((['RunEditor'],), description),
  1104. ]
  1105. ref_to_push = 'abcdef0123456789'
  1106. calls += [
  1107. ((['git', 'config', 'branch.master.merge'],), 'refs/heads/master'),
  1108. ((['git', 'config', 'branch.master.remote'],), 'origin'),
  1109. ]
  1110. if custom_cl_base is None:
  1111. calls += [
  1112. ((['get_or_create_merge_base', 'master',
  1113. 'refs/remotes/origin/master'],),
  1114. 'origin/master'),
  1115. ]
  1116. parent = 'origin/master'
  1117. else:
  1118. calls += [
  1119. ((['git', 'merge-base', '--is-ancestor', custom_cl_base,
  1120. 'refs/remotes/origin/master'],),
  1121. callError(1)), # Means not ancenstor.
  1122. (('ask_for_data',
  1123. 'Do you take responsibility for cleaning up potential mess '
  1124. 'resulting from proceeding with upload? Press Enter to upload, '
  1125. 'or Ctrl+C to abort'), ''),
  1126. ]
  1127. parent = custom_cl_base
  1128. calls += [
  1129. ((['git', 'rev-parse', 'HEAD:'],), # `HEAD:` means HEAD's tree hash.
  1130. '0123456789abcdef'),
  1131. ((['git', 'commit-tree', '0123456789abcdef', '-p', parent,
  1132. '-F', '/tmp/named'],),
  1133. ref_to_push),
  1134. ]
  1135. else:
  1136. ref_to_push = 'HEAD'
  1137. calls += [
  1138. ((['git', 'rev-list',
  1139. (custom_cl_base if custom_cl_base else expected_upstream_ref) + '..' +
  1140. ref_to_push],),
  1141. '1hashPerLine\n'),
  1142. ]
  1143. if notify:
  1144. ref_suffix = '%ready,notify=ALL'
  1145. else:
  1146. if not issue and squash:
  1147. ref_suffix = '%wip'
  1148. else:
  1149. ref_suffix = '%notify=NONE'
  1150. if title:
  1151. ref_suffix += ',m=' + title
  1152. if git_mirror is None:
  1153. calls += [
  1154. ((['git', 'config', 'remote.origin.url'],),
  1155. 'https://chromium.googlesource.com/yyy/zzz'),
  1156. ]
  1157. else:
  1158. calls += [
  1159. ((['git', 'config', 'remote.origin.url'],),
  1160. '/cache/this-dir-exists'),
  1161. (('os.path.isdir', '/cache/this-dir-exists'),
  1162. True),
  1163. # Runs in /cache/this-dir-exists.
  1164. ((['git', 'config', 'remote.origin.url'],),
  1165. 'https://chromium.googlesource.com/yyy/zzz'),
  1166. ]
  1167. calls += [
  1168. ((['git', 'push',
  1169. 'https://chromium.googlesource.com/yyy/zzz',
  1170. ref_to_push + ':refs/for/refs/heads/master' + ref_suffix],),
  1171. ('remote:\n'
  1172. 'remote: Processing changes: (\)\n'
  1173. 'remote: Processing changes: (|)\n'
  1174. 'remote: Processing changes: (/)\n'
  1175. 'remote: Processing changes: (-)\n'
  1176. 'remote: Processing changes: new: 1 (/)\n'
  1177. 'remote: Processing changes: new: 1, done\n'
  1178. 'remote:\n'
  1179. 'remote: New Changes:\n'
  1180. 'remote: https://chromium-review.googlesource.com/#/c/foo/+/123456 '
  1181. 'XXX\n'
  1182. 'remote:\n'
  1183. 'To https://chromium.googlesource.com/yyy/zzz\n'
  1184. ' * [new branch] hhhh -> refs/for/refs/heads/master\n')),
  1185. ]
  1186. if squash:
  1187. calls += [
  1188. ((['git', 'config', 'branch.master.gerritissue', '123456'],),
  1189. ''),
  1190. ((['git', 'config', 'branch.master.gerritserver',
  1191. 'https://chromium-review.googlesource.com'],), ''),
  1192. ((['git', 'config', 'branch.master.gerritsquashhash',
  1193. 'abcdef0123456789'],), ''),
  1194. ]
  1195. calls += [
  1196. ((['git', 'config', 'rietveld.cc'],), ''),
  1197. (('AddReviewers', 'chromium-review.googlesource.com', 123456
  1198. if squash else None, sorted(reviewers),
  1199. ['joe@example.com', 'chromium-reviews+test-more-cc@chromium.org'] +
  1200. cc, notify), ''),
  1201. ]
  1202. if tbr:
  1203. calls += [
  1204. (('GetChangeDetail', 'chromium-review.googlesource.com', '123456',
  1205. ['LABELS']), {
  1206. 'labels': {
  1207. 'Code-Review': {
  1208. 'default_value': 0,
  1209. 'all': [],
  1210. 'values': {
  1211. '+2': 'lgtm, approved',
  1212. '+1': 'lgtm, but someone else must approve',
  1213. ' 0': 'No score',
  1214. '-1': 'Don\'t submit as-is',
  1215. }
  1216. }
  1217. }
  1218. }),
  1219. (('SetReview', 'chromium-review.googlesource.com',
  1220. 123456 if squash else None, 'Self-approving for TBR',
  1221. {'Code-Review': 2}, None), ''),
  1222. ]
  1223. calls += cls._git_post_upload_calls()
  1224. return calls
  1225. def _run_gerrit_upload_test(
  1226. self,
  1227. upload_args,
  1228. description,
  1229. reviewers=None,
  1230. squash=True,
  1231. squash_mode=None,
  1232. expected_upstream_ref='origin/refs/heads/master',
  1233. title=None,
  1234. notify=False,
  1235. post_amend_description=None,
  1236. issue=None,
  1237. cc=None,
  1238. git_mirror=None,
  1239. fetched_status=None,
  1240. other_cl_owner=None,
  1241. custom_cl_base=None,
  1242. tbr=None):
  1243. """Generic gerrit upload test framework."""
  1244. if squash_mode is None:
  1245. if '--no-squash' in upload_args:
  1246. squash_mode = 'nosquash'
  1247. elif '--squash' in upload_args:
  1248. squash_mode = 'squash'
  1249. else:
  1250. squash_mode = 'default'
  1251. reviewers = reviewers or []
  1252. cc = cc or []
  1253. self.mock(git_cl.sys, 'stdout', StringIO.StringIO())
  1254. self.mock(git_cl.gerrit_util, 'CookiesAuthenticator',
  1255. CookiesAuthenticatorMockFactory(
  1256. same_auth=('git-owner.example.com', '', 'pass')))
  1257. self.mock(git_cl._GerritChangelistImpl, '_GerritCommitMsgHookCheck',
  1258. lambda _, offer_removal: None)
  1259. self.mock(git_cl.gclient_utils, 'RunEditor',
  1260. lambda *_, **__: self._mocked_call(['RunEditor']))
  1261. self.mock(git_cl, 'DownloadGerritHook', lambda force: self._mocked_call(
  1262. 'DownloadGerritHook', force))
  1263. original_os_path_isdir = os.path.isdir
  1264. def selective_os_path_isdir_mock(path):
  1265. if path == '/cache/this-dir-exists':
  1266. return self._mocked_call('os.path.isdir', path)
  1267. return original_os_path_isdir(path)
  1268. self.mock(os.path, 'isdir', selective_os_path_isdir_mock)
  1269. self.calls = self._gerrit_base_calls(
  1270. issue=issue,
  1271. fetched_description=description,
  1272. fetched_status=fetched_status,
  1273. other_cl_owner=other_cl_owner,
  1274. custom_cl_base=custom_cl_base)
  1275. if fetched_status != 'ABANDONED':
  1276. self.mock(tempfile, 'NamedTemporaryFile', MakeNamedTemporaryFileMock(
  1277. expected_content=description))
  1278. self.mock(os, 'remove', lambda _: True)
  1279. self.calls += self._gerrit_upload_calls(
  1280. description, reviewers, squash,
  1281. squash_mode=squash_mode,
  1282. expected_upstream_ref=expected_upstream_ref,
  1283. title=title, notify=notify,
  1284. post_amend_description=post_amend_description,
  1285. issue=issue, cc=cc, git_mirror=git_mirror,
  1286. custom_cl_base=custom_cl_base, tbr=tbr)
  1287. # Uncomment when debugging.
  1288. # print '\n'.join(map(lambda x: '%2i: %s' % x, enumerate(self.calls)))
  1289. git_cl.main(['upload'] + upload_args)
  1290. def test_gerrit_upload_without_change_id(self):
  1291. self._run_gerrit_upload_test(
  1292. ['--no-squash'],
  1293. 'desc\n\nBUG=\n',
  1294. [],
  1295. squash=False,
  1296. post_amend_description='desc\n\nBUG=\n\nChange-Id: Ixxx')
  1297. def test_gerrit_upload_without_change_id_override_nosquash(self):
  1298. self._run_gerrit_upload_test(
  1299. [],
  1300. 'desc\n\nBUG=\n',
  1301. [],
  1302. squash=False,
  1303. squash_mode='override_nosquash',
  1304. post_amend_description='desc\n\nBUG=\n\nChange-Id: Ixxx')
  1305. def test_gerrit_no_reviewer(self):
  1306. self._run_gerrit_upload_test(
  1307. [],
  1308. 'desc\n\nBUG=\n\nChange-Id: I123456789\n',
  1309. [],
  1310. squash=False,
  1311. squash_mode='override_nosquash')
  1312. def test_gerrit_patchset_title_special_chars(self):
  1313. self.mock(git_cl.sys, 'stdout', StringIO.StringIO())
  1314. self._run_gerrit_upload_test(
  1315. ['-f', '-t', 'We\'ll escape ^_ ^ special chars...@{u}'],
  1316. 'desc\n\nBUG=\n\nChange-Id: I123456789',
  1317. squash=False,
  1318. squash_mode='override_nosquash',
  1319. title='We%27ll_escape_%5E%5F_%5E_special_chars%2E%2E%2E%40%7Bu%7D')
  1320. def test_gerrit_reviewers_cmd_line(self):
  1321. self._run_gerrit_upload_test(
  1322. ['-r', 'foo@example.com', '--send-mail'],
  1323. 'desc\n\nBUG=\n\nChange-Id: I123456789',
  1324. ['foo@example.com'],
  1325. squash=False,
  1326. squash_mode='override_nosquash',
  1327. notify=True)
  1328. def test_gerrit_reviewer_multiple(self):
  1329. self._run_gerrit_upload_test(
  1330. [],
  1331. 'desc\nTBR=reviewer@example.com\nBUG=\nR=another@example.com\n'
  1332. 'CC=more@example.com,people@example.com\n\n'
  1333. 'Change-Id: 123456789',
  1334. ['reviewer@example.com', 'another@example.com'],
  1335. expected_upstream_ref='origin/master',
  1336. cc=['more@example.com', 'people@example.com'],
  1337. tbr='reviewer@example.com')
  1338. def test_gerrit_upload_squash_first_is_default(self):
  1339. self._run_gerrit_upload_test(
  1340. [],
  1341. 'desc\nBUG=\n\nChange-Id: 123456789',
  1342. [],
  1343. expected_upstream_ref='origin/master')
  1344. def test_gerrit_upload_squash_first(self):
  1345. self._run_gerrit_upload_test(
  1346. ['--squash'],
  1347. 'desc\nBUG=\n\nChange-Id: 123456789',
  1348. [],
  1349. squash=True,
  1350. expected_upstream_ref='origin/master')
  1351. def test_gerrit_upload_squash_first_against_rev(self):
  1352. custom_cl_base = 'custom_cl_base_rev_or_branch'
  1353. self._run_gerrit_upload_test(
  1354. ['--squash', custom_cl_base],
  1355. 'desc\nBUG=\n\nChange-Id: 123456789',
  1356. [],
  1357. squash=True,
  1358. expected_upstream_ref='origin/master',
  1359. custom_cl_base=custom_cl_base)
  1360. self.assertIn(
  1361. 'If you proceed with upload, more than 1 CL may be created by Gerrit',
  1362. sys.stdout.getvalue())
  1363. def test_gerrit_upload_squash_first_with_mirror(self):
  1364. self._run_gerrit_upload_test(
  1365. ['--squash'],
  1366. 'desc\nBUG=\n\nChange-Id: 123456789',
  1367. [],
  1368. squash=True,
  1369. expected_upstream_ref='origin/master',
  1370. git_mirror='https://chromium.googlesource.com/yyy/zzz')
  1371. def test_gerrit_upload_squash_reupload(self):
  1372. description = 'desc\nBUG=\n\nChange-Id: 123456789'
  1373. self._run_gerrit_upload_test(
  1374. ['--squash'],
  1375. description,
  1376. [],
  1377. squash=True,
  1378. expected_upstream_ref='origin/master',
  1379. issue=123456)
  1380. def test_gerrit_upload_squash_reupload_to_abandoned(self):
  1381. self.mock(git_cl, 'DieWithError',
  1382. lambda msg, change=None: self._mocked_call('DieWithError', msg))
  1383. description = 'desc\nBUG=\n\nChange-Id: 123456789'
  1384. with self.assertRaises(SystemExitMock):
  1385. self._run_gerrit_upload_test(
  1386. ['--squash'],
  1387. description,
  1388. [],
  1389. squash=True,
  1390. expected_upstream_ref='origin/master',
  1391. issue=123456,
  1392. fetched_status='ABANDONED')
  1393. def test_gerrit_upload_squash_reupload_to_not_owned(self):
  1394. self.mock(git_cl.gerrit_util, 'GetAccountDetails',
  1395. lambda *_, **__: {'email': 'yet-another@example.com'})
  1396. description = 'desc\nBUG=\n\nChange-Id: 123456789'
  1397. self._run_gerrit_upload_test(
  1398. ['--squash'],
  1399. description,
  1400. [],
  1401. squash=True,
  1402. expected_upstream_ref='origin/master',
  1403. issue=123456,
  1404. other_cl_owner='other@example.com')
  1405. self.assertIn(
  1406. 'WARNING: Change 123456 is owned by other@example.com, but you '
  1407. 'authenticate to Gerrit as yet-another@example.com.\n'
  1408. 'Uploading may fail due to lack of permissions',
  1409. git_cl.sys.stdout.getvalue())
  1410. def test_upload_branch_deps(self):
  1411. self.mock(git_cl.sys, 'stdout', StringIO.StringIO())
  1412. def mock_run_git(*args, **_kwargs):
  1413. if args[0] == ['for-each-ref',
  1414. '--format=%(refname:short) %(upstream:short)',
  1415. 'refs/heads']:
  1416. # Create a local branch dependency tree that looks like this:
  1417. # test1 -> test2 -> test3 -> test4 -> test5
  1418. # -> test3.1
  1419. # test6 -> test0
  1420. branch_deps = [
  1421. 'test2 test1', # test1 -> test2
  1422. 'test3 test2', # test2 -> test3
  1423. 'test3.1 test2', # test2 -> test3.1
  1424. 'test4 test3', # test3 -> test4
  1425. 'test5 test4', # test4 -> test5
  1426. 'test6 test0', # test0 -> test6
  1427. 'test7', # test7
  1428. ]
  1429. return '\n'.join(branch_deps)
  1430. self.mock(git_cl, 'RunGit', mock_run_git)
  1431. class RecordCalls:
  1432. times_called = 0
  1433. record_calls = RecordCalls()
  1434. def mock_CMDupload(*args, **_kwargs):
  1435. record_calls.times_called += 1
  1436. return 0
  1437. self.mock(git_cl, 'CMDupload', mock_CMDupload)
  1438. self.calls = [
  1439. (('ask_for_data', 'This command will checkout all dependent branches '
  1440. 'and run "git cl upload". Press Enter to continue, '
  1441. 'or Ctrl+C to abort'), ''),
  1442. ]
  1443. class MockChangelist():
  1444. def __init__(self):
  1445. pass
  1446. def GetBranch(self):
  1447. return 'test1'
  1448. def GetIssue(self):
  1449. return '123'
  1450. def GetPatchset(self):
  1451. return '1001'
  1452. def IsGerrit(self):
  1453. return False
  1454. ret = git_cl.upload_branch_deps(MockChangelist(), [])
  1455. # CMDupload should have been called 5 times because of 5 dependent branches.
  1456. self.assertEquals(5, record_calls.times_called)
  1457. self.assertEquals(0, ret)
  1458. def test_gerrit_change_id(self):
  1459. self.calls = [
  1460. ((['git', 'write-tree'], ),
  1461. 'hashtree'),
  1462. ((['git', 'rev-parse', 'HEAD~0'], ),
  1463. 'branch-parent'),
  1464. ((['git', 'var', 'GIT_AUTHOR_IDENT'], ),
  1465. 'A B <a@b.org> 1456848326 +0100'),
  1466. ((['git', 'var', 'GIT_COMMITTER_IDENT'], ),
  1467. 'C D <c@d.org> 1456858326 +0100'),
  1468. ((['git', 'hash-object', '-t', 'commit', '--stdin'], ),
  1469. 'hashchange'),
  1470. ]
  1471. change_id = git_cl.GenerateGerritChangeId('line1\nline2\n')
  1472. self.assertEqual(change_id, 'Ihashchange')
  1473. def test_desecription_append_footer(self):
  1474. for init_desc, footer_line, expected_desc in [
  1475. # Use unique desc first lines for easy test failure identification.
  1476. ('foo', 'R=one', 'foo\n\nR=one'),
  1477. ('foo\n\nR=one', 'BUG=', 'foo\n\nR=one\nBUG='),
  1478. ('foo\n\nR=one', 'Change-Id: Ixx', 'foo\n\nR=one\n\nChange-Id: Ixx'),
  1479. ('foo\n\nChange-Id: Ixx', 'R=one', 'foo\n\nR=one\n\nChange-Id: Ixx'),
  1480. ('foo\n\nR=one\n\nChange-Id: Ixx', 'TBR=two',
  1481. 'foo\n\nR=one\nTBR=two\n\nChange-Id: Ixx'),
  1482. ('foo\n\nR=one\n\nChange-Id: Ixx', 'Foo-Bar: baz',
  1483. 'foo\n\nR=one\n\nChange-Id: Ixx\nFoo-Bar: baz'),
  1484. ('foo\n\nChange-Id: Ixx', 'Foo-Bak: baz',
  1485. 'foo\n\nChange-Id: Ixx\nFoo-Bak: baz'),
  1486. ('foo', 'Change-Id: Ixx', 'foo\n\nChange-Id: Ixx'),
  1487. ]:
  1488. desc = git_cl.ChangeDescription(init_desc)
  1489. desc.append_footer(footer_line)
  1490. self.assertEqual(desc.description, expected_desc)
  1491. def test_update_reviewers(self):
  1492. data = [
  1493. ('foo', [], [],
  1494. 'foo'),
  1495. ('foo\nR=xx', [], [],
  1496. 'foo\nR=xx'),
  1497. ('foo\nTBR=xx', [], [],
  1498. 'foo\nTBR=xx'),
  1499. ('foo', ['a@c'], [],
  1500. 'foo\n\nR=a@c'),
  1501. ('foo\nR=xx', ['a@c'], [],
  1502. 'foo\n\nR=a@c, xx'),
  1503. ('foo\nTBR=xx', ['a@c'], [],
  1504. 'foo\n\nR=a@c\nTBR=xx'),
  1505. ('foo\nTBR=xx\nR=yy', ['a@c'], [],
  1506. 'foo\n\nR=a@c, yy\nTBR=xx'),
  1507. ('foo\nBUG=', ['a@c'], [],
  1508. 'foo\nBUG=\nR=a@c'),
  1509. ('foo\nR=xx\nTBR=yy\nR=bar', ['a@c'], [],
  1510. 'foo\n\nR=a@c, bar, xx\nTBR=yy'),
  1511. ('foo', ['a@c', 'b@c'], [],
  1512. 'foo\n\nR=a@c, b@c'),
  1513. ('foo\nBar\n\nR=\nBUG=', ['c@c'], [],
  1514. 'foo\nBar\n\nR=c@c\nBUG='),
  1515. ('foo\nBar\n\nR=\nBUG=\nR=', ['c@c'], [],
  1516. 'foo\nBar\n\nR=c@c\nBUG='),
  1517. # Same as the line before, but full of whitespaces.
  1518. (
  1519. 'foo\nBar\n\n R = \n BUG = \n R = ', ['c@c'], [],
  1520. 'foo\nBar\n\nR=c@c\n BUG =',
  1521. ),
  1522. # Whitespaces aren't interpreted as new lines.
  1523. ('foo BUG=allo R=joe ', ['c@c'], [],
  1524. 'foo BUG=allo R=joe\n\nR=c@c'),
  1525. # Redundant TBRs get promoted to Rs
  1526. ('foo\n\nR=a@c\nTBR=t@c', ['b@c', 'a@c'], ['a@c', 't@c'],
  1527. 'foo\n\nR=a@c, b@c\nTBR=t@c'),
  1528. ]
  1529. expected = [i[-1] for i in data]
  1530. actual = []
  1531. for orig, reviewers, tbrs, _expected in data:
  1532. obj = git_cl.ChangeDescription(orig)
  1533. obj.update_reviewers(reviewers, tbrs)
  1534. actual.append(obj.description)
  1535. self.assertEqual(expected, actual)
  1536. def test_get_hash_tags(self):
  1537. cases = [
  1538. ('', []),
  1539. ('a', []),
  1540. ('[a]', ['a']),
  1541. ('[aa]', ['aa']),
  1542. ('[a ]', ['a']),
  1543. ('[a- ]', ['a']),
  1544. ('[a- b]', ['a-b']),
  1545. ('[a--b]', ['a-b']),
  1546. ('[a', []),
  1547. ('[a]x', ['a']),
  1548. ('[aa]x', ['aa']),
  1549. ('[a b]', ['a-b']),
  1550. ('[a b]', ['a-b']),
  1551. ('[a__b]', ['a-b']),
  1552. ('[a] x', ['a']),
  1553. ('[a][b]', ['a', 'b']),
  1554. ('[a] [b]', ['a', 'b']),
  1555. ('[a][b]x', ['a', 'b']),
  1556. ('[a][b] x', ['a', 'b']),
  1557. ('[a]\n[b]', ['a']),
  1558. ('[a\nb]', []),
  1559. ('[a][', ['a']),
  1560. ('Revert "[a] feature"', ['a']),
  1561. ('Reland "[a] feature"', ['a']),
  1562. ('Revert: [a] feature', ['a']),
  1563. ('Reland: [a] feature', ['a']),
  1564. ('Revert "Reland: [a] feature"', ['a']),
  1565. ('Foo: feature', ['foo']),
  1566. ('Foo Bar: feature', ['foo-bar']),
  1567. ('Revert "Foo bar: feature"', ['foo-bar']),
  1568. ('Reland "Foo bar: feature"', ['foo-bar']),
  1569. ]
  1570. for desc, expected in cases:
  1571. change_desc = git_cl.ChangeDescription(desc)
  1572. actual = change_desc.get_hash_tags()
  1573. self.assertEqual(
  1574. actual,
  1575. expected,
  1576. 'GetHashTags(%r) == %r, expected %r' % (desc, actual, expected))
  1577. def test_get_target_ref(self):
  1578. # Check remote or remote branch not present.
  1579. self.assertEqual(None, git_cl.GetTargetRef('origin', None, 'master'))
  1580. self.assertEqual(None, git_cl.GetTargetRef(None,
  1581. 'refs/remotes/origin/master',
  1582. 'master'))
  1583. # Check default target refs for branches.
  1584. self.assertEqual('refs/heads/master',
  1585. git_cl.GetTargetRef('origin', 'refs/remotes/origin/master',
  1586. None))
  1587. self.assertEqual('refs/heads/master',
  1588. git_cl.GetTargetRef('origin', 'refs/remotes/origin/lkgr',
  1589. None))
  1590. self.assertEqual('refs/heads/master',
  1591. git_cl.GetTargetRef('origin', 'refs/remotes/origin/lkcr',
  1592. None))
  1593. self.assertEqual('refs/branch-heads/123',
  1594. git_cl.GetTargetRef('origin',
  1595. 'refs/remotes/branch-heads/123',
  1596. None))
  1597. self.assertEqual('refs/diff/test',
  1598. git_cl.GetTargetRef('origin',
  1599. 'refs/remotes/origin/refs/diff/test',
  1600. None))
  1601. self.assertEqual('refs/heads/chrome/m42',
  1602. git_cl.GetTargetRef('origin',
  1603. 'refs/remotes/origin/chrome/m42',
  1604. None))
  1605. # Check target refs for user-specified target branch.
  1606. for branch in ('branch-heads/123', 'remotes/branch-heads/123',
  1607. 'refs/remotes/branch-heads/123'):
  1608. self.assertEqual('refs/branch-heads/123',
  1609. git_cl.GetTargetRef('origin',
  1610. 'refs/remotes/origin/master',
  1611. branch))
  1612. for branch in ('origin/master', 'remotes/origin/master',
  1613. 'refs/remotes/origin/master'):
  1614. self.assertEqual('refs/heads/master',
  1615. git_cl.GetTargetRef('origin',
  1616. 'refs/remotes/branch-heads/123',
  1617. branch))
  1618. for branch in ('master', 'heads/master', 'refs/heads/master'):
  1619. self.assertEqual('refs/heads/master',
  1620. git_cl.GetTargetRef('origin',
  1621. 'refs/remotes/branch-heads/123',
  1622. branch))
  1623. def test_patch_when_dirty(self):
  1624. # Patch when local tree is dirty
  1625. self.mock(git_common, 'is_dirty_git_tree', lambda x: True)
  1626. self.assertNotEqual(git_cl.main(['patch', '123456']), 0)
  1627. @staticmethod
  1628. def _get_gerrit_codereview_server_calls(branch, value=None,
  1629. git_short_host='host',
  1630. detect_branch=True,
  1631. detect_server=True):
  1632. """Returns calls executed by _GerritChangelistImpl.GetCodereviewServer.
  1633. If value is given, branch.<BRANCH>.gerritcodereview is already set.
  1634. """
  1635. calls = []
  1636. if detect_branch:
  1637. calls.append(((['git', 'symbolic-ref', 'HEAD'],), branch))
  1638. if detect_server:
  1639. calls.append(((['git', 'config', 'branch.' + branch + '.gerritserver'],),
  1640. CERR1 if value is None else value))
  1641. if value is None:
  1642. calls += [
  1643. ((['git', 'config', 'branch.' + branch + '.merge'],),
  1644. 'refs/heads' + branch),
  1645. ((['git', 'config', 'branch.' + branch + '.remote'],),
  1646. 'origin'),
  1647. ((['git', 'config', 'remote.origin.url'],),
  1648. 'https://%s.googlesource.com/my/repo' % git_short_host),
  1649. ]
  1650. return calls
  1651. def _patch_common(self, default_codereview=None, force_codereview=False,
  1652. new_branch=False, git_short_host='host',
  1653. detect_gerrit_server=True,
  1654. actual_codereview=None):
  1655. self.mock(git_cl.sys, 'stdout', StringIO.StringIO())
  1656. self.mock(git_cl._RietveldChangelistImpl, 'GetMostRecentPatchset',
  1657. lambda x: '60001')
  1658. self.mock(git_cl._RietveldChangelistImpl, 'FetchDescription',
  1659. lambda *a, **kw: 'Description')
  1660. self.mock(git_cl, 'IsGitVersionAtLeast', lambda *args: True)
  1661. if new_branch:
  1662. self.calls = [((['git', 'new-branch', 'master'],), ''),]
  1663. if default_codereview:
  1664. if not force_codereview:
  1665. # These calls detect codereview to use.
  1666. self.calls += [
  1667. ((['git', 'symbolic-ref', 'HEAD'],), 'master'),
  1668. ((['git', 'config', 'branch.master.rietveldissue'],), CERR1),
  1669. ((['git', 'config', 'branch.master.gerritissue'],), CERR1),
  1670. ((['git', 'config', 'rietveld.autoupdate'],), CERR1),
  1671. ]
  1672. if default_codereview == 'gerrit':
  1673. if not force_codereview:
  1674. self.calls += [
  1675. ((['git', 'config', 'gerrit.host'],), 'true'),
  1676. ]
  1677. if detect_gerrit_server:
  1678. self.calls += self._get_gerrit_codereview_server_calls(
  1679. 'master', git_short_host=git_short_host,
  1680. detect_branch=not new_branch and force_codereview)
  1681. actual_codereview = 'gerrit'
  1682. elif default_codereview == 'rietveld':
  1683. self.calls += [
  1684. ((['git', 'config', 'gerrit.host'],), CERR1),
  1685. ((['git', 'config', 'rietveld.server'],), 'codereview.example.com'),
  1686. ((['git', 'config', 'branch.master.rietveldserver',],), CERR1),
  1687. ]
  1688. actual_codereview = 'rietveld'
  1689. if actual_codereview == 'rietveld':
  1690. self.calls += [
  1691. ((['git', 'rev-parse', '--show-cdup'],), ''),
  1692. ]
  1693. if not default_codereview:
  1694. self.calls += [
  1695. ((['git', 'symbolic-ref', 'HEAD'],), 'master'),
  1696. ]
  1697. elif actual_codereview == 'gerrit':
  1698. self.calls += [
  1699. (('GetChangeDetail', git_short_host + '-review.googlesource.com',
  1700. '123456', ['ALL_REVISIONS', 'CURRENT_COMMIT']),
  1701. {
  1702. 'current_revision': '7777777777',
  1703. 'revisions': {
  1704. '1111111111': {
  1705. '_number': 1,
  1706. 'fetch': {'http': {
  1707. 'url': 'https://%s.googlesource.com/my/repo' % git_short_host,
  1708. 'ref': 'refs/changes/56/123456/1',
  1709. }},
  1710. },
  1711. '7777777777': {
  1712. '_number': 7,
  1713. 'fetch': {'http': {
  1714. 'url': 'https://%s.googlesource.com/my/repo' % git_short_host,
  1715. 'ref': 'refs/changes/56/123456/7',
  1716. }},
  1717. },
  1718. },
  1719. }),
  1720. ]
  1721. def _common_patch_rietveld_successful(self, **kwargs):
  1722. self._patch_common(**kwargs)
  1723. self.calls += [
  1724. ((['git', 'config', 'branch.master.rietveldissue', '123456'],),
  1725. ''),
  1726. ((['git', 'config', 'branch.master.rietveldserver',
  1727. 'https://codereview.example.com'],), ''),
  1728. ((['git', 'config', 'branch.master.rietveldpatchset', '60001'],),
  1729. ''),
  1730. ((['git', 'commit', '-m',
  1731. 'Description\n\n' +
  1732. 'patch from issue 123456 at patchset 60001 ' +
  1733. '(http://crrev.com/123456#ps60001)'],), ''),
  1734. ]
  1735. def test_patch_rietveld_default(self):
  1736. self._common_patch_rietveld_successful(default_codereview='rietveld')
  1737. self.assertEqual(git_cl.main(['patch', '123456']), 0)
  1738. def test_patch_rietveld_successful_new_branch(self):
  1739. self._common_patch_rietveld_successful(default_codereview='rietveld',
  1740. new_branch=True)
  1741. self.assertEqual(git_cl.main(['patch', '-b', 'master', '123456']), 0)
  1742. def test_patch_rietveld_guess_by_url(self):
  1743. self._common_patch_rietveld_successful(
  1744. default_codereview=None, # It doesn't matter.
  1745. actual_codereview='rietveld')
  1746. self.assertEqual(git_cl.main(
  1747. ['patch', 'https://codereview.example.com/123456']), 0)
  1748. def test_patch_rietveld_conflict(self):
  1749. self._patch_common(default_codereview='rietveld')
  1750. GitCheckoutMock.conflict = True
  1751. self.assertNotEqual(git_cl.main(['patch', '123456']), 0)
  1752. def test_patch_gerrit_default(self):
  1753. self._patch_common(default_codereview='gerrit', git_short_host='chromium',
  1754. detect_gerrit_server=True)
  1755. self.calls += [
  1756. ((['git', 'config', 'remote.origin.url'],),
  1757. 'https://chromium.googlesource.com/my/repo'),
  1758. ((['git', 'fetch', 'https://chromium.googlesource.com/my/repo',
  1759. 'refs/changes/56/123456/7'],), ''),
  1760. ((['git', 'cherry-pick', 'FETCH_HEAD'],), ''),
  1761. ((['git', 'config', 'branch.master.gerritissue', '123456'],),
  1762. ''),
  1763. ((['git', 'config', 'branch.master.gerritserver',
  1764. 'https://chromium-review.googlesource.com'],), ''),
  1765. ((['git', 'config', 'branch.master.gerritpatchset', '7'],), ''),
  1766. ((['git', 'rev-parse', 'FETCH_HEAD'],), 'deadbeef'),
  1767. ((['git', 'config', 'branch.master.last-upload-hash', 'deadbeef'],), ''),
  1768. ((['git', 'config', 'branch.master.gerritsquashhash', 'deadbeef'],), ''),
  1769. ]
  1770. self.assertEqual(git_cl.main(['patch', '123456']), 0)
  1771. def test_patch_gerrit_force(self):
  1772. self._patch_common(default_codereview='gerrit', force_codereview=True,
  1773. git_short_host='host', detect_gerrit_server=True)
  1774. self.calls += [
  1775. ((['git', 'config', 'remote.origin.url'],),
  1776. 'https://host.googlesource.com/my/repo'),
  1777. ((['git', 'fetch', 'https://host.googlesource.com/my/repo',
  1778. 'refs/changes/56/123456/7'],), ''),
  1779. ((['git', 'reset', '--hard', 'FETCH_HEAD'],), ''),
  1780. ((['git', 'config', 'branch.master.gerritissue', '123456'],),
  1781. ''),
  1782. ((['git', 'config', 'branch.master.gerritserver',
  1783. 'https://host-review.googlesource.com'],), ''),
  1784. ((['git', 'config', 'branch.master.gerritpatchset', '7'],), ''),
  1785. ((['git', 'rev-parse', 'FETCH_HEAD'],), 'deadbeef'),
  1786. ((['git', 'config', 'branch.master.last-upload-hash', 'deadbeef'],), ''),
  1787. ((['git', 'config', 'branch.master.gerritsquashhash', 'deadbeef'],), ''),
  1788. ]
  1789. self.assertEqual(git_cl.main(['patch', '--gerrit', '123456', '--force']), 0)
  1790. def test_patch_gerrit_guess_by_url(self):
  1791. self._patch_common(
  1792. default_codereview=None, # It doesn't matter what's default.
  1793. actual_codereview='gerrit',
  1794. git_short_host='else')
  1795. self.calls += self._get_gerrit_codereview_server_calls(
  1796. 'master', git_short_host='else', detect_server=False)
  1797. self.calls += [
  1798. ((['git', 'fetch', 'https://else.googlesource.com/my/repo',
  1799. 'refs/changes/56/123456/1'],), ''),
  1800. ((['git', 'cherry-pick', 'FETCH_HEAD'],), ''),
  1801. ((['git', 'config', 'branch.master.gerritissue', '123456'],),
  1802. ''),
  1803. ((['git', 'config', 'branch.master.gerritserver',
  1804. 'https://else-review.googlesource.com'],), ''),
  1805. ((['git', 'config', 'branch.master.gerritpatchset', '1'],), ''),
  1806. ((['git', 'rev-parse', 'FETCH_HEAD'],), 'deadbeef'),
  1807. ((['git', 'config', 'branch.master.last-upload-hash', 'deadbeef'],), ''),
  1808. ((['git', 'config', 'branch.master.gerritsquashhash', 'deadbeef'],), ''),
  1809. ]
  1810. self.assertEqual(git_cl.main(
  1811. ['patch', 'https://else-review.googlesource.com/#/c/123456/1']), 0)
  1812. def test_patch_gerrit_guess_by_url_like_rietveld(self):
  1813. self._patch_common(
  1814. default_codereview=None, # It doesn't matter what's default.
  1815. actual_codereview='gerrit',
  1816. git_short_host='else')
  1817. self.calls += self._get_gerrit_codereview_server_calls(
  1818. 'master', git_short_host='else', detect_server=False)
  1819. self.calls += [
  1820. ((['git', 'fetch', 'https://else.googlesource.com/my/repo',
  1821. 'refs/changes/56/123456/1'],), ''),
  1822. ((['git', 'cherry-pick', 'FETCH_HEAD'],), ''),
  1823. ((['git', 'config', 'branch.master.gerritissue', '123456'],),
  1824. ''),
  1825. ((['git', 'config', 'branch.master.gerritserver',
  1826. 'https://else-review.googlesource.com'],), ''),
  1827. ((['git', 'config', 'branch.master.gerritpatchset', '1'],), ''),
  1828. ((['git', 'rev-parse', 'FETCH_HEAD'],), 'deadbeef'),
  1829. ((['git', 'config', 'branch.master.last-upload-hash', 'deadbeef'],), ''),
  1830. ((['git', 'config', 'branch.master.gerritsquashhash', 'deadbeef'],), ''),
  1831. ]
  1832. self.assertEqual(git_cl.main(
  1833. ['patch', 'https://else-review.googlesource.com/123456/1']), 0)
  1834. def test_patch_gerrit_guess_by_url_with_repo(self):
  1835. self._patch_common(
  1836. default_codereview=None, # It doesn't matter what's default.
  1837. actual_codereview='gerrit',
  1838. git_short_host='else')
  1839. self.calls += self._get_gerrit_codereview_server_calls(
  1840. 'master', git_short_host='else', detect_server=False)
  1841. self.calls += [
  1842. ((['git', 'fetch', 'https://else.googlesource.com/my/repo',
  1843. 'refs/changes/56/123456/1'],), ''),
  1844. ((['git', 'cherry-pick', 'FETCH_HEAD'],), ''),
  1845. ((['git', 'config', 'branch.master.gerritissue', '123456'],),
  1846. ''),
  1847. ((['git', 'config', 'branch.master.gerritserver',
  1848. 'https://else-review.googlesource.com'],), ''),
  1849. ((['git', 'config', 'branch.master.gerritpatchset', '1'],), ''),
  1850. ((['git', 'rev-parse', 'FETCH_HEAD'],), 'deadbeef'),
  1851. ((['git', 'config', 'branch.master.last-upload-hash', 'deadbeef'],), ''),
  1852. ((['git', 'config', 'branch.master.gerritsquashhash', 'deadbeef'],), ''),
  1853. ]
  1854. self.assertEqual(git_cl.main(
  1855. ['patch', 'https://else-review.googlesource.com/c/my/repo/+/123456/1']),
  1856. 0)
  1857. def test_patch_gerrit_conflict(self):
  1858. self._patch_common(default_codereview='gerrit', detect_gerrit_server=True,
  1859. git_short_host='chromium')
  1860. self.calls += [
  1861. ((['git', 'config', 'remote.origin.url'],),
  1862. 'https://chromium.googlesource.com/my/repo'),
  1863. ((['git', 'fetch', 'https://chromium.googlesource.com/my/repo',
  1864. 'refs/changes/56/123456/7'],), ''),
  1865. ((['git', 'cherry-pick', 'FETCH_HEAD'],), CERR1),
  1866. ((['DieWithError', 'Command "git cherry-pick FETCH_HEAD" failed.\n'],),
  1867. SystemExitMock()),
  1868. ]
  1869. with self.assertRaises(SystemExitMock):
  1870. git_cl.main(['patch', '123456'])
  1871. def test_patch_gerrit_not_exists(self):
  1872. def notExists(_issue, *_, **kwargs):
  1873. raise git_cl.gerrit_util.GerritError(404, '')
  1874. self.mock(git_cl.gerrit_util, 'GetChangeDetail', notExists)
  1875. self.calls = [
  1876. ((['git', 'symbolic-ref', 'HEAD'],), 'master'),
  1877. ((['git', 'config', 'branch.master.rietveldissue'],), CERR1),
  1878. ((['git', 'config', 'branch.master.gerritissue'],), CERR1),
  1879. ((['git', 'config', 'rietveld.autoupdate'],), CERR1),
  1880. ((['git', 'config', 'gerrit.host'],), 'true'),
  1881. ((['git', 'config', 'branch.master.gerritserver'],), CERR1),
  1882. ((['git', 'config', 'branch.master.merge'],), 'refs/heads/master'),
  1883. ((['git', 'config', 'branch.master.remote'],), 'origin'),
  1884. ((['git', 'config', 'remote.origin.url'],),
  1885. 'https://chromium.googlesource.com/my/repo'),
  1886. ((['DieWithError',
  1887. 'change 123456 at https://chromium-review.googlesource.com does not '
  1888. 'exist or you have no access to it'],), SystemExitMock()),
  1889. ]
  1890. with self.assertRaises(SystemExitMock):
  1891. self.assertEqual(1, git_cl.main(['patch', '123456']))
  1892. def test_patch_gerrit_wrong_repo(self):
  1893. self._patch_common(default_codereview='gerrit', git_short_host='chromium',
  1894. detect_gerrit_server=True)
  1895. self.calls += [
  1896. ((['git', 'config', 'remote.origin.url'],),
  1897. 'https://chromium.googlesource.com/wrong/repo'),
  1898. ((['DieWithError',
  1899. 'Trying to patch a change from '
  1900. 'https://chromium.googlesource.com/my/repo but this repo appears '
  1901. 'to be https://chromium.googlesource.com/wrong/repo.'],),
  1902. SystemExitMock()),
  1903. ]
  1904. with self.assertRaises(SystemExitMock):
  1905. self.assertEqual(1, git_cl.main(['patch', '123456']))
  1906. def _checkout_calls(self):
  1907. return [
  1908. ((['git', 'config', '--local', '--get-regexp',
  1909. 'branch\\..*\\.rietveldissue'], ),
  1910. ('branch.retrying.rietveldissue 1111111111\n'
  1911. 'branch.some-fix.rietveldissue 2222222222\n')),
  1912. ((['git', 'config', '--local', '--get-regexp',
  1913. 'branch\\..*\\.gerritissue'], ),
  1914. ('branch.ger-branch.gerritissue 123456\n'
  1915. 'branch.gbranch654.gerritissue 654321\n')),
  1916. ]
  1917. def test_checkout_gerrit(self):
  1918. """Tests git cl checkout <issue>."""
  1919. self.calls = self._checkout_calls()
  1920. self.calls += [((['git', 'checkout', 'ger-branch'], ), '')]
  1921. self.assertEqual(0, git_cl.main(['checkout', '123456']))
  1922. def test_checkout_rietveld(self):
  1923. """Tests git cl checkout <issue>."""
  1924. self.calls = self._checkout_calls()
  1925. self.calls += [((['git', 'checkout', 'some-fix'], ), '')]
  1926. self.assertEqual(0, git_cl.main(['checkout', '2222222222']))
  1927. def test_checkout_not_found(self):
  1928. """Tests git cl checkout <issue>."""
  1929. self.mock(git_cl.sys, 'stdout', StringIO.StringIO())
  1930. self.calls = self._checkout_calls()
  1931. self.assertEqual(1, git_cl.main(['checkout', '99999']))
  1932. def test_checkout_no_branch_issues(self):
  1933. """Tests git cl checkout <issue>."""
  1934. self.mock(git_cl.sys, 'stdout', StringIO.StringIO())
  1935. self.calls = [
  1936. ((['git', 'config', '--local', '--get-regexp',
  1937. 'branch\\..*\\.rietveldissue'], ), CERR1),
  1938. ((['git', 'config', '--local', '--get-regexp',
  1939. 'branch\\..*\\.gerritissue'], ), CERR1),
  1940. ]
  1941. self.assertEqual(1, git_cl.main(['checkout', '99999']))
  1942. def _test_gerrit_ensure_authenticated_common(self, auth,
  1943. skip_auth_check=False):
  1944. self.mock(git_cl.gerrit_util, 'CookiesAuthenticator',
  1945. CookiesAuthenticatorMockFactory(hosts_with_creds=auth))
  1946. self.mock(git_cl, 'DieWithError',
  1947. lambda msg, change=None: self._mocked_call(['DieWithError', msg]))
  1948. self.calls = self._gerrit_ensure_auth_calls(skip_auth_check=skip_auth_check)
  1949. cl = git_cl.Changelist(codereview='gerrit')
  1950. cl.branch = 'master'
  1951. cl.branchref = 'refs/heads/master'
  1952. cl.lookedup_issue = True
  1953. return cl
  1954. def test_gerrit_ensure_authenticated_missing(self):
  1955. cl = self._test_gerrit_ensure_authenticated_common(auth={
  1956. 'chromium.googlesource.com': ('git-is.ok', '', 'but gerrit is missing'),
  1957. })
  1958. self.calls.append(
  1959. ((['DieWithError',
  1960. 'Credentials for the following hosts are required:\n'
  1961. ' chromium-review.googlesource.com\n'
  1962. 'These are read from ~/.gitcookies (or legacy ~/.netrc)\n'
  1963. 'You can (re)generate your credentials by visiting '
  1964. 'https://chromium-review.googlesource.com/new-password'],), ''),)
  1965. self.assertIsNone(cl.EnsureAuthenticated(force=False))
  1966. def test_gerrit_ensure_authenticated_conflict(self):
  1967. self.mock(git_cl.sys, 'stdout', StringIO.StringIO())
  1968. cl = self._test_gerrit_ensure_authenticated_common(auth={
  1969. 'chromium.googlesource.com':
  1970. ('git-one.example.com', None, 'secret1'),
  1971. 'chromium-review.googlesource.com':
  1972. ('git-other.example.com', None, 'secret2'),
  1973. })
  1974. self.calls.append(
  1975. (('ask_for_data', 'If you know what you are doing '
  1976. 'press Enter to continue, or Ctrl+C to abort'), ''))
  1977. self.assertIsNone(cl.EnsureAuthenticated(force=False))
  1978. def test_gerrit_ensure_authenticated_ok(self):
  1979. cl = self._test_gerrit_ensure_authenticated_common(auth={
  1980. 'chromium.googlesource.com':
  1981. ('git-same.example.com', None, 'secret'),
  1982. 'chromium-review.googlesource.com':
  1983. ('git-same.example.com', None, 'secret'),
  1984. })
  1985. self.assertIsNone(cl.EnsureAuthenticated(force=False))
  1986. def test_gerrit_ensure_authenticated_skipped(self):
  1987. cl = self._test_gerrit_ensure_authenticated_common(
  1988. auth={}, skip_auth_check=True)
  1989. self.assertIsNone(cl.EnsureAuthenticated(force=False))
  1990. def test_cmd_set_commit_rietveld(self):
  1991. self.mock(git_cl._RietveldChangelistImpl, 'SetFlags',
  1992. lambda _, v: self._mocked_call(['SetFlags', v]))
  1993. self.calls = [
  1994. ((['git', 'symbolic-ref', 'HEAD'],), 'feature'),
  1995. ((['git', 'config', 'branch.feature.rietveldissue'],), '123'),
  1996. ((['git', 'config', 'rietveld.autoupdate'],), ''),
  1997. ((['git', 'config', 'rietveld.server'],), ''),
  1998. ((['git', 'config', 'rietveld.server'],), ''),
  1999. ((['git', 'config', 'branch.feature.rietveldserver'],),
  2000. 'https://codereview.chromium.org'),
  2001. ((['SetFlags', {'commit': '1', 'cq_dry_run': '0'}], ), ''),
  2002. ]
  2003. self.assertEqual(0, git_cl.main(['set-commit']))
  2004. def _cmd_set_commit_gerrit_common(self, vote, notify=None):
  2005. self.mock(git_cl.gerrit_util, 'SetReview',
  2006. lambda h, i, labels, notify=None:
  2007. self._mocked_call(['SetReview', h, i, labels, notify]))
  2008. self.calls = [
  2009. ((['git', 'symbolic-ref', 'HEAD'],), 'feature'),
  2010. ((['git', 'config', 'branch.feature.rietveldissue'],), CERR1),
  2011. ((['git', 'config', 'branch.feature.gerritissue'],), '123'),
  2012. ((['git', 'config', 'branch.feature.gerritserver'],),
  2013. 'https://chromium-review.googlesource.com'),
  2014. ((['SetReview', 'chromium-review.googlesource.com', 123,
  2015. {'Commit-Queue': vote}, notify],), ''),
  2016. ]
  2017. def test_cmd_set_commit_gerrit_clear(self):
  2018. self._cmd_set_commit_gerrit_common(0)
  2019. self.assertEqual(0, git_cl.main(['set-commit', '-c']))
  2020. def test_cmd_set_commit_gerrit_dry(self):
  2021. self._cmd_set_commit_gerrit_common(1, notify=False)
  2022. self.assertEqual(0, git_cl.main(['set-commit', '-d']))
  2023. def test_cmd_set_commit_gerrit(self):
  2024. self._cmd_set_commit_gerrit_common(2)
  2025. self.assertEqual(0, git_cl.main(['set-commit']))
  2026. def test_description_display(self):
  2027. out = StringIO.StringIO()
  2028. self.mock(git_cl.sys, 'stdout', out)
  2029. self.mock(git_cl, 'Changelist', ChangelistMock)
  2030. ChangelistMock.desc = 'foo\n'
  2031. self.assertEqual(0, git_cl.main(['description', '-d']))
  2032. self.assertEqual('foo\n', out.getvalue())
  2033. def test_description_rietveld(self):
  2034. out = StringIO.StringIO()
  2035. self.mock(git_cl.sys, 'stdout', out)
  2036. self.mock(git_cl.Changelist, 'GetDescription', lambda *args: 'foobar')
  2037. self.assertEqual(0, git_cl.main([
  2038. 'description', '-d', '--rietveld', 'https://code.review.org/123123']))
  2039. self.assertEqual('foobar\n', out.getvalue())
  2040. def test_StatusFieldOverrideIssueMissingArgs(self):
  2041. out = StringIO.StringIO()
  2042. self.mock(git_cl.sys, 'stderr', out)
  2043. try:
  2044. self.assertEqual(git_cl.main(['status', '--issue', '1']), 0)
  2045. except SystemExit as ex:
  2046. self.assertEqual(ex.code, 2)
  2047. self.assertRegexpMatches(out.getvalue(), r'--issue must be specified')
  2048. out = StringIO.StringIO()
  2049. self.mock(git_cl.sys, 'stderr', out)
  2050. try:
  2051. self.assertEqual(git_cl.main(['status', '--issue', '1', '--rietveld']), 0)
  2052. except SystemExit as ex:
  2053. self.assertEqual(ex.code, 2)
  2054. self.assertRegexpMatches(out.getvalue(), r'--field must be specified')
  2055. def test_StatusFieldOverrideIssue(self):
  2056. out = StringIO.StringIO()
  2057. self.mock(git_cl.sys, 'stdout', out)
  2058. def assertIssue(cl_self, *_args):
  2059. self.assertEquals(cl_self.issue, 1)
  2060. return 'foobar'
  2061. self.mock(git_cl.Changelist, 'GetDescription', assertIssue)
  2062. self.calls = [
  2063. ((['git', 'config', 'rietveld.autoupdate'],), ''),
  2064. ((['git', 'config', 'rietveld.server'],), ''),
  2065. ((['git', 'config', 'rietveld.server'],), ''),
  2066. ]
  2067. self.assertEqual(
  2068. git_cl.main(['status', '--issue', '1', '--rietveld', '--field', 'desc']),
  2069. 0)
  2070. self.assertEqual(out.getvalue(), 'foobar\n')
  2071. def test_SetCloseOverrideIssue(self):
  2072. def assertIssue(cl_self, *_args):
  2073. self.assertEquals(cl_self.issue, 1)
  2074. return 'foobar'
  2075. self.mock(git_cl.Changelist, 'GetDescription', assertIssue)
  2076. self.mock(git_cl.Changelist, 'CloseIssue', lambda *_: None)
  2077. self.calls = [
  2078. ((['git', 'config', 'rietveld.autoupdate'],), ''),
  2079. ((['git', 'config', 'rietveld.server'],), ''),
  2080. ((['git', 'config', 'rietveld.server'],), ''),
  2081. ]
  2082. self.assertEqual(
  2083. git_cl.main(['set-close', '--issue', '1', '--rietveld']), 0)
  2084. def test_SetCommitOverrideIssue(self):
  2085. def assertIssue(cl_self, *_args):
  2086. self.assertEquals(cl_self.issue, 1)
  2087. return 'foobar'
  2088. self.mock(git_cl.Changelist, 'GetDescription', assertIssue)
  2089. self.mock(git_cl.Changelist, 'SetCQState', lambda *_: None)
  2090. self.calls = [
  2091. ((['git', 'config', 'rietveld.autoupdate'],), ''),
  2092. ((['git', 'config', 'rietveld.server'],), ''),
  2093. ((['git', 'config', 'rietveld.server'],), ''),
  2094. ((['git', 'symbolic-ref', 'HEAD'],), ''),
  2095. ((['git', 'config', 'rietveld.server'],), ''),
  2096. ((['git', 'config', 'rietveld.server'],), ''),
  2097. ]
  2098. self.assertEqual(
  2099. git_cl.main(['set-close', '--issue', '1', '--rietveld']), 0)
  2100. def test_description_gerrit(self):
  2101. out = StringIO.StringIO()
  2102. self.mock(git_cl.sys, 'stdout', out)
  2103. self.calls = [
  2104. (('GetChangeDetail', 'code.review.org',
  2105. '123123', ['CURRENT_REVISION', 'CURRENT_COMMIT']),
  2106. {
  2107. 'current_revision': 'sha1',
  2108. 'revisions': {'sha1': {
  2109. 'commit': {'message': 'foobar'},
  2110. }},
  2111. }),
  2112. ]
  2113. self.assertEqual(0, git_cl.main([
  2114. 'description', 'https://code.review.org/123123', '-d', '--gerrit']))
  2115. self.assertEqual('foobar\n', out.getvalue())
  2116. def test_description_set_raw(self):
  2117. out = StringIO.StringIO()
  2118. self.mock(git_cl.sys, 'stdout', out)
  2119. self.mock(git_cl, 'Changelist', ChangelistMock)
  2120. self.mock(git_cl.sys, 'stdin', StringIO.StringIO('hihi'))
  2121. self.assertEqual(0, git_cl.main(['description', '-n', 'hihi']))
  2122. self.assertEqual('hihi', ChangelistMock.desc)
  2123. def test_description_appends_bug_line(self):
  2124. current_desc = 'Some.\n\nChange-Id: xxx'
  2125. def RunEditor(desc, _, **kwargs):
  2126. self.assertEquals(
  2127. '# Enter a description of the change.\n'
  2128. '# This will be displayed on the codereview site.\n'
  2129. '# The first line will also be used as the subject of the review.\n'
  2130. '#--------------------This line is 72 characters long'
  2131. '--------------------\n'
  2132. 'Some.\n\nChange-Id: xxx\nBug: ',
  2133. desc)
  2134. # Simulate user changing something.
  2135. return 'Some.\n\nChange-Id: xxx\nBug: 123'
  2136. def UpdateDescriptionRemote(_, desc, force=False):
  2137. self.assertEquals(desc, 'Some.\n\nChange-Id: xxx\nBug: 123')
  2138. self.mock(git_cl.sys, 'stdout', StringIO.StringIO())
  2139. self.mock(git_cl.Changelist, 'GetDescription',
  2140. lambda *args: current_desc)
  2141. self.mock(git_cl._GerritChangelistImpl, 'UpdateDescriptionRemote',
  2142. UpdateDescriptionRemote)
  2143. self.mock(git_cl.gclient_utils, 'RunEditor', RunEditor)
  2144. self.calls = [
  2145. ((['git', 'symbolic-ref', 'HEAD'],), 'feature'),
  2146. ((['git', 'config', 'branch.feature.gerritissue'],), '123'),
  2147. ((['git', 'config', 'rietveld.autoupdate'],), CERR1),
  2148. ((['git', 'config', 'rietveld.bug-prefix'],), CERR1),
  2149. ((['git', 'config', 'core.editor'],), 'vi'),
  2150. ]
  2151. self.assertEqual(0, git_cl.main(['description', '--gerrit']))
  2152. def test_description_set_stdin(self):
  2153. out = StringIO.StringIO()
  2154. self.mock(git_cl.sys, 'stdout', out)
  2155. self.mock(git_cl, 'Changelist', ChangelistMock)
  2156. self.mock(git_cl.sys, 'stdin', StringIO.StringIO('hi \r\n\t there\n\nman'))
  2157. self.assertEqual(0, git_cl.main(['description', '-n', '-']))
  2158. self.assertEqual('hi\n\t there\n\nman', ChangelistMock.desc)
  2159. def test_archive(self):
  2160. self.mock(git_cl.sys, 'stdout', StringIO.StringIO())
  2161. self.calls = \
  2162. [((['git', 'for-each-ref', '--format=%(refname)', 'refs/heads'],),
  2163. 'refs/heads/master\nrefs/heads/foo\nrefs/heads/bar'),
  2164. ((['git', 'config', 'branch.master.rietveldissue'],), '1'),
  2165. ((['git', 'config', 'rietveld.autoupdate'],), CERR1),
  2166. ((['git', 'config', 'rietveld.server'],), 'codereview.example.com'),
  2167. ((['git', 'config', 'branch.foo.rietveldissue'],), '456'),
  2168. ((['git', 'config', 'branch.bar.rietveldissue'],), CERR1),
  2169. ((['git', 'config', 'branch.bar.gerritissue'],), '789'),
  2170. ((['git', 'symbolic-ref', 'HEAD'],), 'master'),
  2171. ((['git', 'tag', 'git-cl-archived-456-foo', 'foo'],), ''),
  2172. ((['git', 'branch', '-D', 'foo'],), '')]
  2173. self.mock(git_cl, 'get_cl_statuses',
  2174. lambda branches, fine_grained, max_processes:
  2175. [(MockChangelistWithBranchAndIssue('master', 1), 'open'),
  2176. (MockChangelistWithBranchAndIssue('foo', 456), 'closed'),
  2177. (MockChangelistWithBranchAndIssue('bar', 789), 'open')])
  2178. self.assertEqual(0, git_cl.main(['archive', '-f']))
  2179. def test_archive_current_branch_fails(self):
  2180. self.mock(git_cl.sys, 'stdout', StringIO.StringIO())
  2181. self.calls = \
  2182. [((['git', 'for-each-ref', '--format=%(refname)', 'refs/heads'],),
  2183. 'refs/heads/master'),
  2184. ((['git', 'config', 'branch.master.rietveldissue'],), '1'),
  2185. ((['git', 'config', 'rietveld.autoupdate'],), CERR1),
  2186. ((['git', 'config', 'rietveld.server'],), 'codereview.example.com'),
  2187. ((['git', 'symbolic-ref', 'HEAD'],), 'master')]
  2188. self.mock(git_cl, 'get_cl_statuses',
  2189. lambda branches, fine_grained, max_processes:
  2190. [(MockChangelistWithBranchAndIssue('master', 1), 'closed')])
  2191. self.assertEqual(1, git_cl.main(['archive', '-f']))
  2192. def test_archive_dry_run(self):
  2193. self.mock(git_cl.sys, 'stdout', StringIO.StringIO())
  2194. self.calls = \
  2195. [((['git', 'for-each-ref', '--format=%(refname)', 'refs/heads'],),
  2196. 'refs/heads/master\nrefs/heads/foo\nrefs/heads/bar'),
  2197. ((['git', 'config', 'branch.master.rietveldissue'],), '1'),
  2198. ((['git', 'config', 'rietveld.autoupdate'],), CERR1),
  2199. ((['git', 'config', 'rietveld.server'],), 'codereview.example.com'),
  2200. ((['git', 'config', 'branch.foo.rietveldissue'],), '456'),
  2201. ((['git', 'config', 'branch.bar.rietveldissue'],), CERR1),
  2202. ((['git', 'config', 'branch.bar.gerritissue'],), '789'),
  2203. ((['git', 'symbolic-ref', 'HEAD'],), 'master'),]
  2204. self.mock(git_cl, 'get_cl_statuses',
  2205. lambda branches, fine_grained, max_processes:
  2206. [(MockChangelistWithBranchAndIssue('master', 1), 'open'),
  2207. (MockChangelistWithBranchAndIssue('foo', 456), 'closed'),
  2208. (MockChangelistWithBranchAndIssue('bar', 789), 'open')])
  2209. self.assertEqual(0, git_cl.main(['archive', '-f', '--dry-run']))
  2210. def test_archive_no_tags(self):
  2211. self.mock(git_cl.sys, 'stdout', StringIO.StringIO())
  2212. self.calls = \
  2213. [((['git', 'for-each-ref', '--format=%(refname)', 'refs/heads'],),
  2214. 'refs/heads/master\nrefs/heads/foo\nrefs/heads/bar'),
  2215. ((['git', 'config', 'branch.master.rietveldissue'],), '1'),
  2216. ((['git', 'config', 'rietveld.autoupdate'],), CERR1),
  2217. ((['git', 'config', 'rietveld.server'],), 'codereview.example.com'),
  2218. ((['git', 'config', 'branch.foo.rietveldissue'],), '456'),
  2219. ((['git', 'config', 'branch.bar.rietveldissue'],), CERR1),
  2220. ((['git', 'config', 'branch.bar.gerritissue'],), '789'),
  2221. ((['git', 'symbolic-ref', 'HEAD'],), 'master'),
  2222. ((['git', 'branch', '-D', 'foo'],), '')]
  2223. self.mock(git_cl, 'get_cl_statuses',
  2224. lambda branches, fine_grained, max_processes:
  2225. [(MockChangelistWithBranchAndIssue('master', 1), 'open'),
  2226. (MockChangelistWithBranchAndIssue('foo', 456), 'closed'),
  2227. (MockChangelistWithBranchAndIssue('bar', 789), 'open')])
  2228. self.assertEqual(0, git_cl.main(['archive', '-f', '--notags']))
  2229. def test_cmd_issue_erase_existing(self):
  2230. out = StringIO.StringIO()
  2231. self.mock(git_cl.sys, 'stdout', out)
  2232. self.calls = [
  2233. ((['git', 'symbolic-ref', 'HEAD'],), 'feature'),
  2234. ((['git', 'config', 'branch.feature.rietveldissue'],), CERR1),
  2235. ((['git', 'config', 'branch.feature.gerritissue'],), '123'),
  2236. # Let this command raise exception (retcode=1) - it should be ignored.
  2237. ((['git', 'config', '--unset', 'branch.feature.last-upload-hash'],),
  2238. CERR1),
  2239. ((['git', 'config', '--unset', 'branch.feature.gerritissue'],), ''),
  2240. ((['git', 'config', '--unset', 'branch.feature.gerritpatchset'],), ''),
  2241. ((['git', 'config', '--unset', 'branch.feature.gerritserver'],), ''),
  2242. ((['git', 'config', '--unset', 'branch.feature.gerritsquashhash'],),
  2243. ''),
  2244. ((['git', 'log', '-1', '--format=%B'],), 'This is a description'),
  2245. ]
  2246. self.assertEqual(0, git_cl.main(['issue', '0']))
  2247. def test_cmd_issue_erase_existing_with_change_id(self):
  2248. out = StringIO.StringIO()
  2249. self.mock(git_cl.sys, 'stdout', out)
  2250. self.mock(git_cl.Changelist, 'GetDescription',
  2251. lambda _: 'This is a description\n\nChange-Id: Ideadbeef')
  2252. self.calls = [
  2253. ((['git', 'symbolic-ref', 'HEAD'],), 'feature'),
  2254. ((['git', 'config', 'branch.feature.rietveldissue'],), CERR1),
  2255. ((['git', 'config', 'branch.feature.gerritissue'],), '123'),
  2256. # Let this command raise exception (retcode=1) - it should be ignored.
  2257. ((['git', 'config', '--unset', 'branch.feature.last-upload-hash'],),
  2258. CERR1),
  2259. ((['git', 'config', '--unset', 'branch.feature.gerritissue'],), ''),
  2260. ((['git', 'config', '--unset', 'branch.feature.gerritpatchset'],), ''),
  2261. ((['git', 'config', '--unset', 'branch.feature.gerritserver'],), ''),
  2262. ((['git', 'config', '--unset', 'branch.feature.gerritsquashhash'],),
  2263. ''),
  2264. ((['git', 'log', '-1', '--format=%B'],),
  2265. 'This is a description\n\nChange-Id: Ideadbeef'),
  2266. ((['git', 'commit', '--amend', '-m', 'This is a description\n'],), ''),
  2267. ]
  2268. self.assertEqual(0, git_cl.main(['issue', '0']))
  2269. def test_cmd_issue_json(self):
  2270. out = StringIO.StringIO()
  2271. self.mock(git_cl.sys, 'stdout', out)
  2272. self.calls = [
  2273. ((['git', 'symbolic-ref', 'HEAD'],), 'feature'),
  2274. ((['git', 'config', 'branch.feature.rietveldissue'],), '123'),
  2275. ((['git', 'config', 'rietveld.autoupdate'],), ''),
  2276. ((['git', 'config', 'rietveld.server'],),
  2277. 'https://codereview.chromium.org'),
  2278. ((['git', 'config', 'branch.feature.rietveldserver'],), ''),
  2279. (('write_json', 'output.json',
  2280. {'issue': 123, 'issue_url': 'https://codereview.chromium.org/123'}),
  2281. ''),
  2282. ]
  2283. self.assertEqual(0, git_cl.main(['issue', '--json', 'output.json']))
  2284. def test_git_cl_try_default_cq_dry_run(self):
  2285. self.mock(git_cl.Changelist, 'GetChange',
  2286. lambda _, *a: (
  2287. self._mocked_call(['GetChange']+list(a))))
  2288. self.mock(git_cl.presubmit_support, 'DoGetTryMasters',
  2289. lambda *_, **__: (
  2290. self._mocked_call(['DoGetTryMasters'])))
  2291. self.mock(git_cl._RietveldChangelistImpl, 'SetCQState',
  2292. lambda _, s: self._mocked_call(['SetCQState', s]))
  2293. self.calls = [
  2294. ((['git', 'symbolic-ref', 'HEAD'],), 'feature'),
  2295. ((['git', 'config', 'branch.feature.rietveldissue'],), '123'),
  2296. ((['git', 'config', 'rietveld.autoupdate'],), ''),
  2297. ((['git', 'config', 'rietveld.server'],),
  2298. 'https://codereview.chromium.org'),
  2299. ((['git', 'config', 'branch.feature.rietveldserver'],), ''),
  2300. ((['git', 'config', 'branch.feature.merge'],), 'feature'),
  2301. ((['git', 'config', 'branch.feature.remote'],), 'origin'),
  2302. ((['get_or_create_merge_base', 'feature', 'feature'],),
  2303. 'fake_ancestor_sha'),
  2304. ((['GetChange', 'fake_ancestor_sha', None], ),
  2305. git_cl.presubmit_support.GitChange(
  2306. '', '', '', '', '', '', '', '')),
  2307. ((['git', 'rev-parse', '--show-cdup'],), '../'),
  2308. ((['DoGetTryMasters'], ), None),
  2309. ((['SetCQState', git_cl._CQState.DRY_RUN], ), None),
  2310. ]
  2311. out = StringIO.StringIO()
  2312. self.mock(git_cl.sys, 'stdout', out)
  2313. self.assertEqual(0, git_cl.main(['try']))
  2314. self.assertEqual(
  2315. out.getvalue(),
  2316. 'Scheduling CQ dry run on: https://codereview.chromium.org/123\n')
  2317. def test_git_cl_try_default_cq_dry_run_gerrit(self):
  2318. self.mock(git_cl.Changelist, 'GetChange',
  2319. lambda _, *a: (
  2320. self._mocked_call(['GetChange']+list(a))))
  2321. self.mock(git_cl.presubmit_support, 'DoGetTryMasters',
  2322. lambda *_, **__: (
  2323. self._mocked_call(['DoGetTryMasters'])))
  2324. self.mock(git_cl._GerritChangelistImpl, 'SetCQState',
  2325. lambda _, s: self._mocked_call(['SetCQState', s]))
  2326. self.calls = [
  2327. ((['git', 'symbolic-ref', 'HEAD'],), 'feature'),
  2328. ((['git', 'config', 'branch.feature.rietveldissue'],), CERR1),
  2329. ((['git', 'config', 'branch.feature.gerritissue'],), '123456'),
  2330. ((['git', 'config', 'branch.feature.gerritserver'],),
  2331. 'https://chromium-review.googlesource.com'),
  2332. (('GetChangeDetail', 'chromium-review.googlesource.com', '123456',
  2333. ['DETAILED_ACCOUNTS', 'ALL_REVISIONS', 'CURRENT_COMMIT']), {
  2334. 'project': 'depot_tools',
  2335. 'status': 'OPEN',
  2336. 'owner': {'email': 'owner@e.mail'},
  2337. 'revisions': {
  2338. 'deadbeaf': {
  2339. '_number': 6,
  2340. },
  2341. 'beeeeeef': {
  2342. '_number': 7,
  2343. 'fetch': {'http': {
  2344. 'url': 'https://chromium.googlesource.com/depot_tools',
  2345. 'ref': 'refs/changes/56/123456/7'
  2346. }},
  2347. },
  2348. },
  2349. }),
  2350. ((['git', 'config', 'branch.feature.merge'],), 'feature'),
  2351. ((['git', 'config', 'branch.feature.remote'],), 'origin'),
  2352. ((['get_or_create_merge_base', 'feature', 'feature'],),
  2353. 'fake_ancestor_sha'),
  2354. ((['GetChange', 'fake_ancestor_sha', None], ),
  2355. git_cl.presubmit_support.GitChange(
  2356. '', '', '', '', '', '', '', '')),
  2357. ((['git', 'rev-parse', '--show-cdup'],), '../'),
  2358. ((['DoGetTryMasters'], ), None),
  2359. ((['SetCQState', git_cl._CQState.DRY_RUN], ), None),
  2360. ]
  2361. out = StringIO.StringIO()
  2362. self.mock(git_cl.sys, 'stdout', out)
  2363. self.assertEqual(0, git_cl.main(['try']))
  2364. self.assertEqual(
  2365. out.getvalue(),
  2366. 'Scheduling CQ dry run on: '
  2367. 'https://chromium-review.googlesource.com/123456\n')
  2368. def test_git_cl_try_buildbucket_with_properties_rietveld(self):
  2369. self.mock(git_cl._RietveldChangelistImpl, 'GetIssueProperties',
  2370. lambda _: {
  2371. 'owner_email': 'owner@e.mail',
  2372. 'private': False,
  2373. 'closed': False,
  2374. 'project': 'depot_tools',
  2375. 'patchsets': [20001],
  2376. })
  2377. self.mock(git_cl.uuid, 'uuid4', lambda: 'uuid4')
  2378. self.calls = [
  2379. ((['git', 'symbolic-ref', 'HEAD'],), 'feature'),
  2380. ((['git', 'config', 'branch.feature.rietveldissue'],), '123'),
  2381. ((['git', 'config', 'rietveld.autoupdate'],), CERR1),
  2382. ((['git', 'config', 'rietveld.server'],),
  2383. 'https://codereview.chromium.org'),
  2384. ((['git', 'config', 'branch.feature.rietveldpatchset'],), '20001'),
  2385. ((['git', 'config', 'branch.feature.rietveldserver'],), CERR1),
  2386. ]
  2387. def _buildbucket_retry(*_, **kw):
  2388. # self.maxDiff = 10000
  2389. body = json.loads(kw['body'])
  2390. self.assertEqual(len(body['builds']), 1)
  2391. build = body['builds'][0]
  2392. params = json.loads(build.pop('parameters_json'))
  2393. self.assertEqual(params, {
  2394. u'builder_name': u'win',
  2395. u'changes': [{u'author': {u'email': u'owner@e.mail'},
  2396. u'revision': None}],
  2397. u'properties': {
  2398. u'category': u'git_cl_try',
  2399. u'issue': 123,
  2400. u'key': u'val',
  2401. u'json': [{u'a': 1}, None],
  2402. u'master': u'tryserver.chromium',
  2403. u'patch_project': u'depot_tools',
  2404. u'patch_storage': u'rietveld',
  2405. u'patchset': 20001,
  2406. u'rietveld': u'https://codereview.chromium.org',
  2407. }
  2408. })
  2409. self.assertEqual(build, {
  2410. u'bucket': u'master.tryserver.chromium',
  2411. u'client_operation_id': u'uuid4',
  2412. u'tags': [u'builder:win',
  2413. u'buildset:patch/rietveld/codereview.chromium.org/123/20001',
  2414. u'user_agent:git_cl_try',
  2415. u'master:tryserver.chromium'],
  2416. })
  2417. self.mock(git_cl, '_buildbucket_retry', _buildbucket_retry)
  2418. self.mock(git_cl.sys, 'stdout', StringIO.StringIO())
  2419. self.assertEqual(0, git_cl.main([
  2420. 'try', '-m', 'tryserver.chromium', '-b', 'win',
  2421. '-p', 'key=val', '-p', 'json=[{"a":1}, null]']))
  2422. self.assertRegexpMatches(
  2423. git_cl.sys.stdout.getvalue(),
  2424. 'Tried jobs on:\nBucket: master.tryserver.chromium')
  2425. def test_git_cl_try_buildbucket_with_properties_gerrit(self):
  2426. self.mock(git_cl.Changelist, 'GetMostRecentPatchset', lambda _: 7)
  2427. self.mock(git_cl.uuid, 'uuid4', lambda: 'uuid4')
  2428. self.calls = [
  2429. ((['git', 'symbolic-ref', 'HEAD'],), 'feature'),
  2430. ((['git', 'config', 'branch.feature.rietveldissue'],), CERR1),
  2431. ((['git', 'config', 'branch.feature.gerritissue'],), '123456'),
  2432. ((['git', 'config', 'branch.feature.gerritserver'],),
  2433. 'https://chromium-review.googlesource.com'),
  2434. (('GetChangeDetail', 'chromium-review.googlesource.com', '123456',
  2435. ['DETAILED_ACCOUNTS', 'ALL_REVISIONS', 'CURRENT_COMMIT']), {
  2436. 'project': 'depot_tools',
  2437. 'status': 'OPEN',
  2438. 'owner': {'email': 'owner@e.mail'},
  2439. 'revisions': {
  2440. 'deadbeaf': {
  2441. '_number': 6,
  2442. },
  2443. 'beeeeeef': {
  2444. '_number': 7,
  2445. 'fetch': {'http': {
  2446. 'url': 'https://chromium.googlesource.com/depot_tools',
  2447. 'ref': 'refs/changes/56/123456/7'
  2448. }},
  2449. },
  2450. },
  2451. }),
  2452. ]
  2453. def _buildbucket_retry(*_, **kw):
  2454. # self.maxDiff = 10000
  2455. body = json.loads(kw['body'])
  2456. self.assertEqual(len(body['builds']), 1)
  2457. build = body['builds'][0]
  2458. params = json.loads(build.pop('parameters_json'))
  2459. self.assertEqual(params, {
  2460. u'builder_name': u'win',
  2461. u'changes': [{u'author': {u'email': u'owner@e.mail'},
  2462. u'revision': None}],
  2463. u'properties': {
  2464. u'category': u'git_cl_try',
  2465. u'key': u'val',
  2466. u'json': [{u'a': 1}, None],
  2467. u'master': u'tryserver.chromium',
  2468. u'patch_gerrit_url':
  2469. u'https://chromium-review.googlesource.com',
  2470. u'patch_issue': 123456,
  2471. u'patch_project': u'depot_tools',
  2472. u'patch_ref': u'refs/changes/56/123456/7',
  2473. u'patch_repository_url':
  2474. u'https://chromium.googlesource.com/depot_tools',
  2475. u'patch_set': 7,
  2476. u'patch_storage': u'gerrit',
  2477. }
  2478. })
  2479. self.assertEqual(build, {
  2480. u'bucket': u'master.tryserver.chromium',
  2481. u'client_operation_id': u'uuid4',
  2482. u'tags': [
  2483. u'builder:win',
  2484. u'buildset:patch/gerrit/chromium-review.googlesource.com/123456/7',
  2485. u'user_agent:git_cl_try',
  2486. u'master:tryserver.chromium'],
  2487. })
  2488. self.mock(git_cl, '_buildbucket_retry', _buildbucket_retry)
  2489. self.mock(git_cl.sys, 'stdout', StringIO.StringIO())
  2490. self.assertEqual(0, git_cl.main([
  2491. 'try', '-m', 'tryserver.chromium', '-b', 'win',
  2492. '-p', 'key=val', '-p', 'json=[{"a":1}, null]']))
  2493. self.assertRegexpMatches(
  2494. git_cl.sys.stdout.getvalue(),
  2495. 'Tried jobs on:\nBucket: master.tryserver.chromium')
  2496. def test_git_cl_try_buildbucket_bucket_flag(self):
  2497. self.mock(git_cl._RietveldChangelistImpl, 'GetIssueProperties',
  2498. lambda _: {
  2499. 'owner_email': 'owner@e.mail',
  2500. 'private': False,
  2501. 'closed': False,
  2502. 'project': 'depot_tools',
  2503. 'patchsets': [20001],
  2504. })
  2505. self.mock(git_cl.uuid, 'uuid4', lambda: 'uuid4')
  2506. self.calls = [
  2507. ((['git', 'symbolic-ref', 'HEAD'],), 'feature'),
  2508. ((['git', 'config', 'branch.feature.rietveldissue'],), '123'),
  2509. ((['git', 'config', 'rietveld.autoupdate'],), CERR1),
  2510. ((['git', 'config', 'rietveld.server'],),
  2511. 'https://codereview.chromium.org'),
  2512. ((['git', 'config', 'branch.feature.rietveldpatchset'],), '20001'),
  2513. ((['git', 'config', 'branch.feature.rietveldserver'],), CERR1),
  2514. ]
  2515. def _buildbucket_retry(*_, **kw):
  2516. body = json.loads(kw['body'])
  2517. self.assertEqual(len(body['builds']), 1)
  2518. build = body['builds'][0]
  2519. params = json.loads(build.pop('parameters_json'))
  2520. self.assertEqual(params, {
  2521. u'builder_name': u'win',
  2522. u'changes': [{u'author': {u'email': u'owner@e.mail'},
  2523. u'revision': None}],
  2524. u'properties': {
  2525. u'category': u'git_cl_try',
  2526. u'issue': 123,
  2527. u'patch_project': u'depot_tools',
  2528. u'patch_storage': u'rietveld',
  2529. u'patchset': 20001,
  2530. u'rietveld': u'https://codereview.chromium.org',
  2531. }
  2532. })
  2533. self.assertEqual(build, {
  2534. u'bucket': u'test.bucket',
  2535. u'client_operation_id': u'uuid4',
  2536. u'tags': [u'builder:win',
  2537. u'buildset:patch/rietveld/codereview.chromium.org/123/20001',
  2538. u'user_agent:git_cl_try'],
  2539. })
  2540. self.mock(git_cl, '_buildbucket_retry', _buildbucket_retry)
  2541. self.mock(git_cl.sys, 'stdout', StringIO.StringIO())
  2542. self.assertEqual(0, git_cl.main([
  2543. 'try', '-B', 'test.bucket', '-b', 'win']))
  2544. self.assertRegexpMatches(
  2545. git_cl.sys.stdout.getvalue(),
  2546. 'Tried jobs on:\nBucket: test.bucket')
  2547. def test_git_cl_try_bots_on_multiple_masters(self):
  2548. self.mock(git_cl.Changelist, 'GetMostRecentPatchset', lambda _: 20001)
  2549. self.calls = [
  2550. ((['git', 'symbolic-ref', 'HEAD'],), 'feature'),
  2551. ((['git', 'config', 'branch.feature.rietveldissue'],), '123'),
  2552. ((['git', 'config', 'rietveld.autoupdate'],), CERR1),
  2553. ((['git', 'config', 'rietveld.server'],),
  2554. 'https://codereview.chromium.org'),
  2555. ((['git', 'config', 'branch.feature.rietveldserver'],), CERR1),
  2556. ((['git', 'config', 'branch.feature.rietveldpatchset'],), '20001'),
  2557. ]
  2558. def _buildbucket_retry(*_, **kw):
  2559. body = json.loads(kw['body'])
  2560. self.assertEqual(len(body['builds']), 2)
  2561. self.assertEqual(body['builds'][0]['bucket'], 'bucket1')
  2562. params = json.loads(body['builds'][0]['parameters_json'])
  2563. self.assertEqual(params['builder_name'], 'builder1')
  2564. self.assertEqual(body['builds'][1]['bucket'], 'bucket2')
  2565. params = json.loads(body['builds'][1]['parameters_json'])
  2566. self.assertEqual(params['builder_name'], 'builder2')
  2567. self.mock(git_cl, '_buildbucket_retry', _buildbucket_retry)
  2568. self.mock(git_cl.urllib2, 'urlopen', lambda _: StringIO.StringIO(
  2569. json.dumps({
  2570. 'builder1': {'bucket': 'bucket1'},
  2571. 'builder2': {'bucket': 'bucket2'},
  2572. })))
  2573. self.mock(git_cl.sys, 'stdout', StringIO.StringIO())
  2574. self.assertEqual(
  2575. 0, git_cl.main(['try', '-b', 'builder1', '-b', 'builder2']))
  2576. self.assertEqual(
  2577. git_cl.sys.stdout.getvalue(),
  2578. 'Tried jobs on:\n'
  2579. 'Bucket: bucket1\n'
  2580. ' builder1: []\n'
  2581. 'Bucket: bucket2\n'
  2582. ' builder2: []\n'
  2583. 'To see results here, run: git cl try-results\n'
  2584. 'To see results in browser, run: git cl web\n')
  2585. def _common_GerritCommitMsgHookCheck(self):
  2586. self.mock(git_cl.sys, 'stdout', StringIO.StringIO())
  2587. self.mock(git_cl.os.path, 'abspath',
  2588. lambda path: self._mocked_call(['abspath', path]))
  2589. self.mock(git_cl.os.path, 'exists',
  2590. lambda path: self._mocked_call(['exists', path]))
  2591. self.mock(git_cl.gclient_utils, 'FileRead',
  2592. lambda path: self._mocked_call(['FileRead', path]))
  2593. self.mock(git_cl.gclient_utils, 'rm_file_or_tree',
  2594. lambda path: self._mocked_call(['rm_file_or_tree', path]))
  2595. self.calls = [
  2596. ((['git', 'rev-parse', '--show-cdup'],), '../'),
  2597. ((['abspath', '../'],), '/abs/git_repo_root'),
  2598. ]
  2599. return git_cl.Changelist(codereview='gerrit', issue=123)
  2600. def test_GerritCommitMsgHookCheck_custom_hook(self):
  2601. cl = self._common_GerritCommitMsgHookCheck()
  2602. self.calls += [
  2603. ((['exists', '/abs/git_repo_root/.git/hooks/commit-msg'],), True),
  2604. ((['FileRead', '/abs/git_repo_root/.git/hooks/commit-msg'],),
  2605. '#!/bin/sh\necho "custom hook"')
  2606. ]
  2607. cl._codereview_impl._GerritCommitMsgHookCheck(offer_removal=True)
  2608. def test_GerritCommitMsgHookCheck_not_exists(self):
  2609. cl = self._common_GerritCommitMsgHookCheck()
  2610. self.calls += [
  2611. ((['exists', '/abs/git_repo_root/.git/hooks/commit-msg'],), False),
  2612. ]
  2613. cl._codereview_impl._GerritCommitMsgHookCheck(offer_removal=True)
  2614. def test_GerritCommitMsgHookCheck(self):
  2615. cl = self._common_GerritCommitMsgHookCheck()
  2616. self.calls += [
  2617. ((['exists', '/abs/git_repo_root/.git/hooks/commit-msg'],), True),
  2618. ((['FileRead', '/abs/git_repo_root/.git/hooks/commit-msg'],),
  2619. '...\n# From Gerrit Code Review\n...\nadd_ChangeId()\n'),
  2620. (('ask_for_data', 'Do you want to remove it now? [Yes/No]: '), 'Yes'),
  2621. ((['rm_file_or_tree', '/abs/git_repo_root/.git/hooks/commit-msg'],),
  2622. ''),
  2623. ]
  2624. cl._codereview_impl._GerritCommitMsgHookCheck(offer_removal=True)
  2625. def test_GerritCmdLand(self):
  2626. self.calls += [
  2627. ((['git', 'symbolic-ref', 'HEAD'],), 'feature'),
  2628. ((['git', 'config', 'branch.feature.gerritsquashhash'],),
  2629. 'deadbeaf'),
  2630. ((['git', 'diff', 'deadbeaf'],), ''), # No diff.
  2631. ((['git', 'config', 'branch.feature.gerritserver'],),
  2632. 'chromium-review.googlesource.com'),
  2633. ]
  2634. cl = git_cl.Changelist(issue=123, codereview='gerrit')
  2635. cl._codereview_impl._GetChangeDetail = lambda _: {
  2636. 'labels': {},
  2637. 'current_revision': 'deadbeaf',
  2638. }
  2639. cl._codereview_impl._GetChangeCommit = lambda: {
  2640. 'commit': 'deadbeef',
  2641. 'web_links': [{'name': 'gitiles',
  2642. 'url': 'https://git.googlesource.com/test/+/deadbeef'}],
  2643. }
  2644. cl._codereview_impl.SubmitIssue = lambda wait_for_merge: None
  2645. out = StringIO.StringIO()
  2646. self.mock(sys, 'stdout', out)
  2647. self.assertEqual(0, cl.CMDLand(force=True,
  2648. bypass_hooks=True,
  2649. verbose=True,
  2650. parallel=False))
  2651. self.assertRegexpMatches(out.getvalue(), 'Issue.*123 has been submitted')
  2652. self.assertRegexpMatches(out.getvalue(), 'Landed as: .*deadbeef')
  2653. BUILDBUCKET_BUILDS_MAP = {
  2654. '9000': {
  2655. 'id': '9000',
  2656. 'bucket': 'master.x.y',
  2657. 'created_by': 'user:someone@chromium.org',
  2658. 'created_ts': '147200002222000',
  2659. 'experimental': False,
  2660. 'parameters_json': json.dumps({
  2661. 'builder_name': 'my-bot',
  2662. 'properties': {'category': 'cq'},
  2663. }),
  2664. 'status': 'STARTED',
  2665. 'tags': [
  2666. 'build_address:x.y/my-bot/2',
  2667. 'builder:my-bot',
  2668. 'experimental:false',
  2669. 'user_agent:cq',
  2670. ],
  2671. 'url': 'http://build.cr.org/p/x.y/builders/my-bot/builds/2',
  2672. },
  2673. '8000': {
  2674. 'id': '8000',
  2675. 'bucket': 'master.x.y',
  2676. 'created_by': 'user:someone@chromium.org',
  2677. 'created_ts': '147200001111000',
  2678. 'experimental': False,
  2679. 'failure_reason': 'BUILD_FAILURE',
  2680. 'parameters_json': json.dumps({
  2681. 'builder_name': 'my-bot',
  2682. 'properties': {'category': 'cq'},
  2683. }),
  2684. 'result_details_json': json.dumps({
  2685. 'properties': {'buildnumber': 1},
  2686. }),
  2687. 'result': 'FAILURE',
  2688. 'status': 'COMPLETED',
  2689. 'tags': [
  2690. 'build_address:x.y/my-bot/1',
  2691. 'builder:my-bot',
  2692. 'experimental:false',
  2693. 'user_agent:cq',
  2694. ],
  2695. 'url': 'http://build.cr.org/p/x.y/builders/my-bot/builds/1',
  2696. },
  2697. }
  2698. def test_write_try_results_json(self):
  2699. expected_output = [
  2700. {
  2701. 'bucket': 'master.x.y',
  2702. 'buildbucket_id': '8000',
  2703. 'builder_name': 'my-bot',
  2704. 'created_ts': '147200001111000',
  2705. 'experimental': False,
  2706. 'failure_reason': 'BUILD_FAILURE',
  2707. 'result': 'FAILURE',
  2708. 'status': 'COMPLETED',
  2709. 'tags': [
  2710. 'build_address:x.y/my-bot/1',
  2711. 'builder:my-bot',
  2712. 'experimental:false',
  2713. 'user_agent:cq',
  2714. ],
  2715. 'url': 'http://build.cr.org/p/x.y/builders/my-bot/builds/1',
  2716. },
  2717. {
  2718. 'bucket': 'master.x.y',
  2719. 'buildbucket_id': '9000',
  2720. 'builder_name': 'my-bot',
  2721. 'created_ts': '147200002222000',
  2722. 'experimental': False,
  2723. 'failure_reason': None,
  2724. 'result': None,
  2725. 'status': 'STARTED',
  2726. 'tags': [
  2727. 'build_address:x.y/my-bot/2',
  2728. 'builder:my-bot',
  2729. 'experimental:false',
  2730. 'user_agent:cq',
  2731. ],
  2732. 'url': 'http://build.cr.org/p/x.y/builders/my-bot/builds/2',
  2733. },
  2734. ]
  2735. self.calls = [(('write_json', 'output.json', expected_output), '')]
  2736. git_cl.write_try_results_json('output.json', self.BUILDBUCKET_BUILDS_MAP)
  2737. def _setup_fetch_try_jobs(self, most_recent_patchset=20001):
  2738. out = StringIO.StringIO()
  2739. self.mock(sys, 'stdout', out)
  2740. self.mock(git_cl.Changelist, 'GetMostRecentPatchset',
  2741. lambda *args: most_recent_patchset)
  2742. self.mock(git_cl.auth, 'get_authenticator_for_host', lambda host, _cfg:
  2743. self._mocked_call(['get_authenticator_for_host', host]))
  2744. self.mock(git_cl, '_buildbucket_retry', lambda *_, **__:
  2745. self._mocked_call(['_buildbucket_retry']))
  2746. def _setup_fetch_try_jobs_rietveld(self, *request_results):
  2747. self._setup_fetch_try_jobs(most_recent_patchset=20001)
  2748. self.calls += [
  2749. ((['git', 'symbolic-ref', 'HEAD'],), 'feature'),
  2750. ((['git', 'config', 'branch.feature.rietveldissue'],), '1'),
  2751. ((['git', 'config', 'rietveld.autoupdate'],), CERR1),
  2752. ((['git', 'config', 'rietveld.server'],), 'codereview.example.com'),
  2753. ((['git', 'config', 'branch.feature.rietveldpatchset'],), '20001'),
  2754. ((['git', 'config', 'branch.feature.rietveldserver'],),
  2755. 'codereview.example.com'),
  2756. ((['get_authenticator_for_host', 'codereview.example.com'],),
  2757. AuthenticatorMock()),
  2758. ] + [((['_buildbucket_retry'],), r) for r in request_results]
  2759. def test_fetch_try_jobs_none_rietveld(self):
  2760. self._setup_fetch_try_jobs_rietveld({})
  2761. # Simulate that user isn't logged in.
  2762. self.mock(AuthenticatorMock, 'has_cached_credentials', lambda _: False)
  2763. self.assertEqual(0, git_cl.main(['try-results']))
  2764. self.assertRegexpMatches(sys.stdout.getvalue(),
  2765. 'Warning: Some results might be missing')
  2766. self.assertRegexpMatches(sys.stdout.getvalue(), 'No try jobs')
  2767. def test_fetch_try_jobs_some_rietveld(self):
  2768. self._setup_fetch_try_jobs_rietveld({
  2769. 'builds': self.BUILDBUCKET_BUILDS_MAP.values(),
  2770. })
  2771. self.assertEqual(0, git_cl.main(['try-results']))
  2772. self.assertRegexpMatches(sys.stdout.getvalue(), '^Failures:')
  2773. self.assertRegexpMatches(sys.stdout.getvalue(), 'Started:')
  2774. self.assertRegexpMatches(sys.stdout.getvalue(), '2 try jobs')
  2775. def _setup_fetch_try_jobs_gerrit(self, *request_results):
  2776. self._setup_fetch_try_jobs(most_recent_patchset=13)
  2777. self.calls += [
  2778. ((['git', 'symbolic-ref', 'HEAD'],), 'feature'),
  2779. ((['git', 'config', 'branch.feature.rietveldissue'],), CERR1),
  2780. ((['git', 'config', 'branch.feature.gerritissue'],), '1'),
  2781. # TODO(tandrii): Uncomment the below if we decide to support checking
  2782. # patchsets for Gerrit.
  2783. # Simulate that Gerrit has more patchsets than local.
  2784. # ((['git', 'config', 'branch.feature.gerritpatchset'],), '12'),
  2785. ((['git', 'config', 'branch.feature.gerritserver'],),
  2786. 'https://x-review.googlesource.com'),
  2787. ((['get_authenticator_for_host', 'x-review.googlesource.com'],),
  2788. AuthenticatorMock()),
  2789. ] + [((['_buildbucket_retry'],), r) for r in request_results]
  2790. def test_fetch_try_jobs_none_gerrit(self):
  2791. self._setup_fetch_try_jobs_gerrit({})
  2792. self.assertEqual(0, git_cl.main(['try-results']))
  2793. # TODO(tandrii): Uncomment the below if we decide to support checking
  2794. # patchsets for Gerrit.
  2795. # self.assertRegexpMatches(
  2796. # sys.stdout.getvalue(),
  2797. # r'Warning: Codereview server has newer patchsets \(13\)')
  2798. self.assertRegexpMatches(sys.stdout.getvalue(), 'No try jobs')
  2799. def test_fetch_try_jobs_some_gerrit(self):
  2800. self._setup_fetch_try_jobs_gerrit({
  2801. 'builds': self.BUILDBUCKET_BUILDS_MAP.values(),
  2802. })
  2803. # TODO(tandrii): Uncomment the below if we decide to support checking
  2804. # patchsets for Gerrit.
  2805. # self.calls.remove(
  2806. # ((['git', 'config', 'branch.feature.gerritpatchset'],), '12'))
  2807. self.assertEqual(0, git_cl.main(['try-results', '--patchset', '5']))
  2808. # ... and doesn't result in warning.
  2809. self.assertNotRegexpMatches(sys.stdout.getvalue(), 'Warning')
  2810. self.assertRegexpMatches(sys.stdout.getvalue(), '^Failures:')
  2811. self.assertRegexpMatches(sys.stdout.getvalue(), 'Started:')
  2812. self.assertRegexpMatches(sys.stdout.getvalue(), '2 try jobs')
  2813. def _mock_gerrit_changes_for_detail_cache(self):
  2814. self.mock(git_cl._GerritChangelistImpl, '_GetGerritHost', lambda _: 'host')
  2815. def test_gerrit_change_detail_cache_normalize(self):
  2816. self._mock_gerrit_changes_for_detail_cache()
  2817. self.calls = [
  2818. (('GetChangeDetail', 'host', '2', ['CASE']), 'b'),
  2819. ]
  2820. cl = git_cl.Changelist(codereview='gerrit')
  2821. self.assertEqual(cl._GetChangeDetail(issue=2, options=['CaSe']), 'b')
  2822. self.assertEqual(cl._GetChangeDetail(issue=2, options=['CASE']), 'b')
  2823. self.assertEqual(cl._GetChangeDetail(issue='2'), 'b')
  2824. self.assertEqual(cl._GetChangeDetail(issue=2), 'b')
  2825. def test_gerrit_change_detail_cache_simple(self):
  2826. self._mock_gerrit_changes_for_detail_cache()
  2827. self.calls = [
  2828. (('GetChangeDetail', 'host', '1', []), 'a'),
  2829. (('GetChangeDetail', 'host', '2', []), 'b'),
  2830. (('GetChangeDetail', 'host', '2', []), 'b2'),
  2831. ]
  2832. cl = git_cl.Changelist(issue=1, codereview='gerrit')
  2833. self.assertEqual(cl._GetChangeDetail(), 'a') # Miss.
  2834. self.assertEqual(cl._GetChangeDetail(), 'a')
  2835. self.assertEqual(cl._GetChangeDetail(issue=2), 'b') # Miss.
  2836. self.assertEqual(cl._GetChangeDetail(issue=2, no_cache=True), 'b2') # Miss.
  2837. self.assertEqual(cl._GetChangeDetail(), 'a')
  2838. self.assertEqual(cl._GetChangeDetail(issue=2), 'b2')
  2839. def test_gerrit_change_detail_cache_options(self):
  2840. self._mock_gerrit_changes_for_detail_cache()
  2841. self.calls = [
  2842. (('GetChangeDetail', 'host', '1', ['C', 'A', 'B']), 'cab'),
  2843. (('GetChangeDetail', 'host', '1', ['A', 'D']), 'ad'),
  2844. (('GetChangeDetail', 'host', '1', ['A']), 'a'), # no_cache=True
  2845. (('GetChangeDetail', 'host', '1', ['B']), 'b'), # no longer in cache.
  2846. ]
  2847. cl = git_cl.Changelist(issue=1, codereview='gerrit')
  2848. self.assertEqual(cl._GetChangeDetail(options=['C', 'A', 'B']), 'cab')
  2849. self.assertEqual(cl._GetChangeDetail(options=['A', 'B', 'C']), 'cab')
  2850. self.assertEqual(cl._GetChangeDetail(options=['B', 'A']), 'cab')
  2851. self.assertEqual(cl._GetChangeDetail(options=['C']), 'cab')
  2852. self.assertEqual(cl._GetChangeDetail(options=['A']), 'cab')
  2853. self.assertEqual(cl._GetChangeDetail(), 'cab')
  2854. self.assertEqual(cl._GetChangeDetail(options=['A', 'D']), 'ad')
  2855. self.assertEqual(cl._GetChangeDetail(options=['A']), 'cab')
  2856. self.assertEqual(cl._GetChangeDetail(options=['D']), 'ad')
  2857. self.assertEqual(cl._GetChangeDetail(), 'cab')
  2858. # Finally, no_cache should invalidate all caches for given change.
  2859. self.assertEqual(cl._GetChangeDetail(options=['A'], no_cache=True), 'a')
  2860. self.assertEqual(cl._GetChangeDetail(options=['B']), 'b')
  2861. def test_gerrit_description_caching(self):
  2862. def gen_detail(rev, desc):
  2863. return {
  2864. 'current_revision': rev,
  2865. 'revisions': {rev: {'commit': {'message': desc}}}
  2866. }
  2867. self.calls = [
  2868. (('GetChangeDetail', 'host', '1',
  2869. ['CURRENT_REVISION', 'CURRENT_COMMIT']),
  2870. gen_detail('rev1', 'desc1')),
  2871. (('GetChangeDetail', 'host', '1',
  2872. ['CURRENT_REVISION', 'CURRENT_COMMIT']),
  2873. gen_detail('rev2', 'desc2')),
  2874. ]
  2875. self._mock_gerrit_changes_for_detail_cache()
  2876. cl = git_cl.Changelist(issue=1, codereview='gerrit')
  2877. self.assertEqual(cl.GetDescription(), 'desc1')
  2878. self.assertEqual(cl.GetDescription(), 'desc1') # cache hit.
  2879. self.assertEqual(cl.GetDescription(force=True), 'desc2')
  2880. def test_print_current_creds(self):
  2881. class CookiesAuthenticatorMock(object):
  2882. def __init__(self):
  2883. self.gitcookies = {
  2884. 'host.googlesource.com': ('user', 'pass'),
  2885. 'host-review.googlesource.com': ('user', 'pass'),
  2886. }
  2887. self.netrc = self
  2888. self.netrc.hosts = {
  2889. 'github.com': ('user2', None, 'pass2'),
  2890. 'host2.googlesource.com': ('user3', None, 'pass'),
  2891. }
  2892. self.mock(git_cl.gerrit_util, 'CookiesAuthenticator',
  2893. CookiesAuthenticatorMock)
  2894. self.mock(sys, 'stdout', StringIO.StringIO())
  2895. git_cl._GitCookiesChecker().print_current_creds(include_netrc=True)
  2896. self.assertEqual(list(sys.stdout.getvalue().splitlines()), [
  2897. ' Host\t User\t Which file',
  2898. '============================\t=====\t===========',
  2899. 'host-review.googlesource.com\t user\t.gitcookies',
  2900. ' host.googlesource.com\t user\t.gitcookies',
  2901. ' host2.googlesource.com\tuser3\t .netrc',
  2902. ])
  2903. sys.stdout.buf = ''
  2904. git_cl._GitCookiesChecker().print_current_creds(include_netrc=False)
  2905. self.assertEqual(list(sys.stdout.getvalue().splitlines()), [
  2906. ' Host\tUser\t Which file',
  2907. '============================\t====\t===========',
  2908. 'host-review.googlesource.com\tuser\t.gitcookies',
  2909. ' host.googlesource.com\tuser\t.gitcookies',
  2910. ])
  2911. def _common_creds_check_mocks(self):
  2912. def exists_mock(path):
  2913. dirname = os.path.dirname(path)
  2914. if dirname == os.path.expanduser('~'):
  2915. dirname = '~'
  2916. base = os.path.basename(path)
  2917. if base in ('.netrc', '.gitcookies'):
  2918. return self._mocked_call('os.path.exists', '%s/%s' % (dirname, base))
  2919. # git cl also checks for existence other files not relevant to this test.
  2920. return None
  2921. self.mock(os.path, 'exists', exists_mock)
  2922. self.mock(sys, 'stdout', StringIO.StringIO())
  2923. def test_creds_check_gitcookies_not_configured(self):
  2924. self._common_creds_check_mocks()
  2925. self.mock(git_cl._GitCookiesChecker, 'get_hosts_with_creds',
  2926. lambda _, include_netrc=False: [])
  2927. self.calls = [
  2928. ((['git', 'config', '--path', 'http.cookiefile'],), CERR1),
  2929. ((['git', 'config', '--global', 'http.cookiefile'],), CERR1),
  2930. (('os.path.exists', '~/.netrc'), True),
  2931. (('ask_for_data', 'Press Enter to setup .gitcookies, '
  2932. 'or Ctrl+C to abort'), ''),
  2933. ((['git', 'config', '--global', 'http.cookiefile',
  2934. os.path.expanduser('~/.gitcookies')], ), ''),
  2935. ]
  2936. self.assertEqual(0, git_cl.main(['creds-check']))
  2937. self.assertRegexpMatches(
  2938. sys.stdout.getvalue(),
  2939. '^You seem to be using outdated .netrc for git credentials:')
  2940. self.assertRegexpMatches(
  2941. sys.stdout.getvalue(),
  2942. '\nConfigured git to use .gitcookies from')
  2943. def test_creds_check_gitcookies_configured_custom_broken(self):
  2944. self._common_creds_check_mocks()
  2945. self.mock(git_cl._GitCookiesChecker, 'get_hosts_with_creds',
  2946. lambda _, include_netrc=False: [])
  2947. self.calls = [
  2948. ((['git', 'config', '--path', 'http.cookiefile'],), CERR1),
  2949. ((['git', 'config', '--global', 'http.cookiefile'],),
  2950. '/custom/.gitcookies'),
  2951. (('os.path.exists', '/custom/.gitcookies'), False),
  2952. (('ask_for_data', 'Reconfigure git to use default .gitcookies? '
  2953. 'Press Enter to reconfigure, or Ctrl+C to abort'), ''),
  2954. ((['git', 'config', '--global', 'http.cookiefile',
  2955. os.path.expanduser('~/.gitcookies')], ), ''),
  2956. ]
  2957. self.assertEqual(0, git_cl.main(['creds-check']))
  2958. self.assertRegexpMatches(
  2959. sys.stdout.getvalue(),
  2960. 'WARNING: You have configured custom path to .gitcookies: ')
  2961. self.assertRegexpMatches(
  2962. sys.stdout.getvalue(),
  2963. 'However, your configured .gitcookies file is missing.')
  2964. def test_git_cl_comment_add_rietveld(self):
  2965. self.mock(git_cl._RietveldChangelistImpl, 'AddComment',
  2966. lambda _, message, publish: self._mocked_call(
  2967. 'AddComment', message, publish))
  2968. self.calls = [
  2969. ((['git', 'config', 'rietveld.autoupdate'],), CERR1),
  2970. ((['git', 'config', 'rietveld.server'],), 'codereview.chromium.org'),
  2971. (('AddComment', 'msg', None), ''),
  2972. ]
  2973. self.assertEqual(0, git_cl.main(['comment', '--rietveld',
  2974. '-i', '10', '-a', 'msg']))
  2975. def test_git_cl_comment_add_gerrit(self):
  2976. self.mock(git_cl.gerrit_util, 'SetReview',
  2977. lambda host, change, msg, ready:
  2978. self._mocked_call('SetReview', host, change, msg, ready))
  2979. self.calls = [
  2980. ((['git', 'symbolic-ref', 'HEAD'],), CERR1),
  2981. ((['git', 'symbolic-ref', 'HEAD'],), CERR1),
  2982. ((['git', 'config', 'rietveld.upstream-branch'],), CERR1),
  2983. ((['git', 'branch', '-r'],), 'origin/HEAD -> origin/master\n'
  2984. 'origin/master'),
  2985. ((['git', 'config', 'remote.origin.url'],),
  2986. 'https://chromium.googlesource.com/infra/infra'),
  2987. (('SetReview', 'chromium-review.googlesource.com', 10, 'msg', None),
  2988. None),
  2989. ]
  2990. self.assertEqual(0, git_cl.main(['comment', '--gerrit', '-i', '10',
  2991. '-a', 'msg']))
  2992. def test_git_cl_comments_fetch_rietveld(self):
  2993. self.mock(sys, 'stdout', StringIO.StringIO())
  2994. self.calls = [
  2995. ((['git', 'config', 'rietveld.autoupdate'],), CERR1),
  2996. ((['git', 'config', 'rietveld.server'],), 'codereview.chromium.org'),
  2997. ] * 2
  2998. self.mock(git_cl._RietveldChangelistImpl, 'GetIssueProperties', lambda _: {
  2999. 'messages': [
  3000. {'text': 'lgtm', 'date': '2017-03-13 20:49:34.515270',
  3001. 'disapproval': False, 'approval': True, 'sender': 'r@example.com'},
  3002. {'text': 'not lgtm', 'date': '2017-03-13 21:50:34.515270',
  3003. 'disapproval': True, 'approval': False, 'sender': 'r2@example.com'},
  3004. # Intentionally wrong order here.
  3005. {'text': 'PTAL', 'date': '2000-03-13 20:49:34.515270',
  3006. 'disapproval': False, 'approval': False,
  3007. 'sender': 'owner@example.com'},
  3008. ],
  3009. 'owner_email': 'owner@example.com',
  3010. })
  3011. expected_comments_summary = [
  3012. git_cl._CommentSummary(
  3013. message='lgtm',
  3014. date=datetime.datetime(2017, 3, 13, 20, 49, 34, 515270),
  3015. disapproval=False, approval=True, sender='r@example.com'),
  3016. git_cl._CommentSummary(
  3017. message='not lgtm',
  3018. date=datetime.datetime(2017, 3, 13, 21, 50, 34, 515270),
  3019. disapproval=True, approval=False, sender='r2@example.com'),
  3020. # Note: same order as in whatever Rietveld returns.
  3021. git_cl._CommentSummary(
  3022. message='PTAL',
  3023. date=datetime.datetime(2000, 3, 13, 20, 49, 34, 515270),
  3024. disapproval=False, approval=False, sender='owner@example.com'),
  3025. ]
  3026. cl = git_cl.Changelist(codereview='rietveld', issue=1)
  3027. self.assertEqual(cl.GetCommentsSummary(), expected_comments_summary)
  3028. with git_cl.gclient_utils.temporary_directory() as tempdir:
  3029. out_file = os.path.abspath(os.path.join(tempdir, 'out.json'))
  3030. self.assertEqual(0, git_cl.main(['comment', '--rietveld', '-i', '10',
  3031. '-j', out_file]))
  3032. with open(out_file) as f:
  3033. read = json.load(f)
  3034. self.assertEqual(len(read), 3)
  3035. self.assertEqual(read[0], {
  3036. 'date': '2000-03-13 20:49:34.515270',
  3037. 'message': 'PTAL',
  3038. 'approval': False,
  3039. 'disapproval': False,
  3040. 'sender': 'owner@example.com'})
  3041. self.assertEqual(read[1]['date'], '2017-03-13 20:49:34.515270')
  3042. self.assertEqual(read[2]['date'], '2017-03-13 21:50:34.515270')
  3043. def test_git_cl_comments_fetch_gerrit(self):
  3044. self.mock(sys, 'stdout', StringIO.StringIO())
  3045. self.calls = [
  3046. ((['git', 'symbolic-ref', 'HEAD'],), CERR1),
  3047. ((['git', 'symbolic-ref', 'HEAD'],), CERR1),
  3048. ((['git', 'config', 'rietveld.upstream-branch'],), CERR1),
  3049. ((['git', 'branch', '-r'],), 'origin/HEAD -> origin/master\n'
  3050. 'origin/master'),
  3051. ((['git', 'config', 'remote.origin.url'],),
  3052. 'https://chromium.googlesource.com/infra/infra'),
  3053. (('GetChangeDetail', 'chromium-review.googlesource.com', '1',
  3054. ['MESSAGES', 'DETAILED_ACCOUNTS']), {
  3055. 'owner': {'email': 'owner@example.com'},
  3056. 'messages': [
  3057. {
  3058. u'_revision_number': 1,
  3059. u'author': {
  3060. u'_account_id': 1111084,
  3061. u'email': u'commit-bot@chromium.org',
  3062. u'name': u'Commit Bot'
  3063. },
  3064. u'date': u'2017-03-15 20:08:45.000000000',
  3065. u'id': u'f5a6c25ecbd3b3b54a43ae418ed97eff046dc50b',
  3066. u'message': u'Patch Set 1:\n\nDry run: CQ is trying the patch...',
  3067. u'tag': u'autogenerated:cq:dry-run'
  3068. },
  3069. {
  3070. u'_revision_number': 2,
  3071. u'author': {
  3072. u'_account_id': 11151243,
  3073. u'email': u'owner@example.com',
  3074. u'name': u'owner'
  3075. },
  3076. u'date': u'2017-03-16 20:00:41.000000000',
  3077. u'id': u'f5a6c25ecbd3b3b54a43ae418ed97eff046d1234',
  3078. u'message': u'PTAL',
  3079. },
  3080. {
  3081. u'_revision_number': 2,
  3082. u'author': {
  3083. u'_account_id': 148512 ,
  3084. u'email': u'reviewer@example.com',
  3085. u'name': u'reviewer'
  3086. },
  3087. u'date': u'2017-03-17 05:19:37.500000000',
  3088. u'id': u'f5a6c25ecbd3b3b54a43ae418ed97eff046d4568',
  3089. u'message': u'Patch Set 2: Code-Review+1',
  3090. },
  3091. ]
  3092. }),
  3093. (('GetChangeComments', 'chromium-review.googlesource.com', 1), {
  3094. '/COMMIT_MSG': [
  3095. {
  3096. 'author': {'email': u'reviewer@example.com'},
  3097. 'updated': u'2017-03-17 05:19:37.500000000',
  3098. 'patch_set': 2,
  3099. 'side': 'REVISION',
  3100. 'message': 'Please include a bug link',
  3101. },
  3102. ],
  3103. 'codereview.settings': [
  3104. {
  3105. 'author': {'email': u'owner@example.com'},
  3106. 'updated': u'2017-03-16 20:00:41.000000000',
  3107. 'patch_set': 2,
  3108. 'side': 'PARENT',
  3109. 'line': 42,
  3110. 'message': 'I removed this because it is bad',
  3111. },
  3112. ]
  3113. }),
  3114. ] * 2
  3115. expected_comments_summary = [
  3116. git_cl._CommentSummary(
  3117. message=(
  3118. u'PTAL\n' +
  3119. u'\n' +
  3120. u'codereview.settings\n' +
  3121. u' Base, Line 42: https://chromium-review.googlesource.com/' +
  3122. u'c/1/2/codereview.settings#b42\n' +
  3123. u' I removed this because it is bad\n'),
  3124. date=datetime.datetime(2017, 3, 16, 20, 0, 41, 0),
  3125. disapproval=False, approval=False, sender=u'owner@example.com'),
  3126. git_cl._CommentSummary(
  3127. message=(
  3128. u'Patch Set 2: Code-Review+1\n' +
  3129. u'\n' +
  3130. u'/COMMIT_MSG\n' +
  3131. u' PS2, File comment: https://chromium-review.googlesource.com/' +
  3132. u'c/1/2//COMMIT_MSG#\n' +
  3133. u' Please include a bug link\n'),
  3134. date=datetime.datetime(2017, 3, 17, 5, 19, 37, 500000),
  3135. disapproval=False, approval=False, sender=u'reviewer@example.com'),
  3136. ]
  3137. cl = git_cl.Changelist(codereview='gerrit', issue=1)
  3138. self.assertEqual(cl.GetCommentsSummary(), expected_comments_summary)
  3139. with git_cl.gclient_utils.temporary_directory() as tempdir:
  3140. out_file = os.path.abspath(os.path.join(tempdir, 'out.json'))
  3141. self.assertEqual(0, git_cl.main(['comment', '--gerrit', '-i', '1',
  3142. '-j', out_file]))
  3143. with open(out_file) as f:
  3144. read = json.load(f)
  3145. self.assertEqual(len(read), 2)
  3146. self.assertEqual(read[0], {
  3147. u'date': u'2017-03-16 20:00:41.000000',
  3148. u'message': (
  3149. u'PTAL\n' +
  3150. u'\n' +
  3151. u'codereview.settings\n' +
  3152. u' Base, Line 42: https://chromium-review.googlesource.com/' +
  3153. u'c/1/2/codereview.settings#b42\n' +
  3154. u' I removed this because it is bad\n'),
  3155. u'approval': False,
  3156. u'disapproval': False,
  3157. u'sender': u'owner@example.com'})
  3158. self.assertEqual(read[1], {
  3159. u'date': u'2017-03-17 05:19:37.500000',
  3160. u'message': (
  3161. u'Patch Set 2: Code-Review+1\n' +
  3162. u'\n' +
  3163. u'/COMMIT_MSG\n' +
  3164. u' PS2, File comment: https://chromium-review.googlesource.com/' +
  3165. u'c/1/2//COMMIT_MSG#\n' +
  3166. u' Please include a bug link\n'),
  3167. u'approval': False,
  3168. u'disapproval': False,
  3169. u'sender': u'reviewer@example.com'})
  3170. if __name__ == '__main__':
  3171. logging.basicConfig(
  3172. level=logging.DEBUG if '-v' in sys.argv else logging.ERROR)
  3173. unittest.main()