presubmit_unittest.py 152 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633
  1. #!/usr/bin/env vpython3
  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 presubmit_support.py and presubmit_canned_checks.py."""
  6. # pylint: disable=no-member,E1103
  7. import functools
  8. import io
  9. import itertools
  10. import logging
  11. import multiprocessing
  12. import os
  13. import random
  14. import re
  15. import sys
  16. import tempfile
  17. import threading
  18. import time
  19. import unittest
  20. from io import StringIO
  21. from unittest import mock
  22. import urllib.request as urllib_request
  23. _ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  24. sys.path.insert(0, _ROOT)
  25. from testing_support.test_case_utils import TestCaseUtils
  26. import auth
  27. import gclient_utils
  28. import git_cl
  29. import git_common as git
  30. import json
  31. import owners_client
  32. import presubmit_support as presubmit
  33. import rdb_wrapper
  34. import scm
  35. import subprocess2 as subprocess
  36. # Shortcut.
  37. presubmit_canned_checks = presubmit.presubmit_canned_checks
  38. RUNNING_PY_CHECKS_TEXT = ('Running presubmit upload checks ...\n')
  39. # Access to a protected member XXX of a client class
  40. # pylint: disable=protected-access
  41. # TODO: Should fix these warnings.
  42. # pylint: disable=line-too-long
  43. class MockTemporaryFile(object):
  44. """Simple mock for files returned by tempfile.NamedTemporaryFile()."""
  45. def __init__(self, name):
  46. self.name = name
  47. def __enter__(self):
  48. return self
  49. def __exit__(self, *args):
  50. pass
  51. def close(self):
  52. pass
  53. class PresubmitTestsBase(TestCaseUtils, unittest.TestCase):
  54. """Sets up and tears down the mocks but doesn't test anything as-is."""
  55. presubmit_text = """
  56. def CheckChangeOnUpload(input_api, output_api):
  57. if input_api.change.tags.get('ERROR'):
  58. return [output_api.PresubmitError("!!")]
  59. if input_api.change.tags.get('PROMPT_WARNING'):
  60. return [output_api.PresubmitPromptWarning("??")]
  61. else:
  62. return ()
  63. def PostUploadHook(gerrit, change, output_api):
  64. if change.tags.get('ERROR'):
  65. return [output_api.PresubmitError("!!")]
  66. if change.tags.get('PROMPT_WARNING'):
  67. return [output_api.PresubmitPromptWarning("??")]
  68. else:
  69. return ()
  70. """
  71. presubmit_trymaster = """
  72. def GetPreferredTryMasters(project, change):
  73. return %s
  74. """
  75. presubmit_diffs = """
  76. diff --git %(filename)s %(filename)s
  77. index fe3de7b..54ae6e1 100755
  78. --- %(filename)s 2011-02-09 10:38:16.517224845 -0800
  79. +++ %(filename)s 2011-02-09 10:38:53.177226516 -0800
  80. @@ -1,6 +1,5 @@
  81. this is line number 0
  82. this is line number 1
  83. -this is line number 2 to be deleted
  84. this is line number 3
  85. this is line number 4
  86. this is line number 5
  87. @@ -8,7 +7,7 @@
  88. this is line number 7
  89. this is line number 8
  90. this is line number 9
  91. -this is line number 10 to be modified
  92. +this is line number 10
  93. this is line number 11
  94. this is line number 12
  95. this is line number 13
  96. @@ -21,9 +20,8 @@
  97. this is line number 20
  98. this is line number 21
  99. this is line number 22
  100. -this is line number 23
  101. -this is line number 24
  102. -this is line number 25
  103. +this is line number 23.1
  104. +this is line number 25.1
  105. this is line number 26
  106. this is line number 27
  107. this is line number 28
  108. @@ -31,6 +29,7 @@
  109. this is line number 30
  110. this is line number 31
  111. this is line number 32
  112. +this is line number 32.1
  113. this is line number 33
  114. this is line number 34
  115. this is line number 35
  116. @@ -38,14 +37,14 @@
  117. this is line number 37
  118. this is line number 38
  119. this is line number 39
  120. -
  121. this is line number 40
  122. -this is line number 41
  123. +this is line number 41.1
  124. this is line number 42
  125. this is line number 43
  126. this is line number 44
  127. this is line number 45
  128. +
  129. this is line number 46
  130. this is line number 47
  131. -this is line number 48
  132. +this is line number 48.1
  133. this is line number 49
  134. """
  135. def setUp(self):
  136. super(PresubmitTestsBase, self).setUp()
  137. # Disable string diff max. It's hard to parse assertion error if there's
  138. # limit set.
  139. self.maxDiff = None
  140. class FakeChange(object):
  141. def __init__(self, obj):
  142. self._root = obj.fake_root_dir
  143. self.issue = 0
  144. def RepositoryRoot(self):
  145. return self._root
  146. def UpstreamBranch(self):
  147. return 'upstream'
  148. presubmit._ASKED_FOR_FEEDBACK = False
  149. self.fake_root_dir = self.RootDir()
  150. self.fake_change = FakeChange(self)
  151. self.rdb_client = mock.MagicMock()
  152. mock.patch('gclient_utils.FileRead').start()
  153. mock.patch('gclient_utils.FileWrite').start()
  154. mock.patch('json.load').start()
  155. mock.patch('multiprocessing.cpu_count', lambda: 2)
  156. mock.patch('gerrit_util.IsCodeOwnersEnabledOnHost').start()
  157. mock.patch('os.chdir').start()
  158. mock.patch('os.getcwd', self.RootDir)
  159. mock.patch('os.listdir').start()
  160. mock.patch('os.path.abspath', lambda f: f).start()
  161. mock.patch('os.path.isfile').start()
  162. mock.patch('os.remove').start()
  163. mock.patch('presubmit_support._parse_files').start()
  164. mock.patch('presubmit_support.rdb_wrapper.client',
  165. return_value=self.rdb_client).start()
  166. mock.patch('presubmit_support.sigint_handler').start()
  167. mock.patch('presubmit_support.time_time', return_value=0).start()
  168. mock.patch('presubmit_support.warn').start()
  169. mock.patch('random.randint').start()
  170. mock.patch('scm.GIT.GenerateDiff').start()
  171. mock.patch('scm.GIT.GetBranch', lambda x: None).start()
  172. mock.patch('scm.determine_scm').start()
  173. mock.patch('subprocess2.Popen').start()
  174. mock.patch('sys.stderr', StringIO()).start()
  175. mock.patch('sys.stdout', StringIO()).start()
  176. mock.patch('tempfile.NamedTemporaryFile').start()
  177. mock.patch('threading.Timer').start()
  178. mock.patch('urllib.request.urlopen').start()
  179. self.addCleanup(mock.patch.stopall)
  180. def checkstdout(self, value):
  181. self.assertEqual(sys.stdout.getvalue(), value)
  182. class PresubmitUnittest(PresubmitTestsBase):
  183. """General presubmit_support.py tests (excluding InputApi and OutputApi)."""
  184. _INHERIT_SETTINGS = 'inherit-review-settings-ok'
  185. fake_root_dir = '/foo/bar'
  186. def testCannedCheckFilter(self):
  187. canned = presubmit.presubmit_canned_checks
  188. orig = canned.CheckOwners
  189. with presubmit.canned_check_filter(['CheckOwners']):
  190. self.assertNotEqual(canned.CheckOwners, orig)
  191. self.assertEqual(canned.CheckOwners(None, None), [])
  192. self.assertEqual(canned.CheckOwners, orig)
  193. def testListRelevantPresubmitFiles(self):
  194. files = [
  195. 'blat.cc',
  196. os.path.join('foo', 'haspresubmit', 'yodle', 'smart.h'),
  197. os.path.join('moo', 'mat', 'gat', 'yo.h'),
  198. os.path.join('foo', 'luck.h'),
  199. ]
  200. known_files = [
  201. os.path.join(self.fake_root_dir, 'PRESUBMIT.py'),
  202. os.path.join(self.fake_root_dir, 'foo', 'haspresubmit',
  203. 'PRESUBMIT.py'),
  204. os.path.join(self.fake_root_dir, 'foo', 'haspresubmit', 'yodle',
  205. 'PRESUBMIT.py'),
  206. ]
  207. os.path.isfile.side_effect = lambda f: f in known_files
  208. dirs_with_presubmit = [
  209. self.fake_root_dir,
  210. os.path.join(self.fake_root_dir, 'foo', 'haspresubmit'),
  211. os.path.join(self.fake_root_dir, 'foo', 'haspresubmit', 'yodle'),
  212. ]
  213. os.listdir.side_effect = (lambda d: ['PRESUBMIT.py']
  214. if d in dirs_with_presubmit else [])
  215. presubmit_files = presubmit.ListRelevantPresubmitFiles(
  216. files, self.fake_root_dir)
  217. self.assertEqual(presubmit_files, known_files)
  218. def testListUserPresubmitFiles(self):
  219. files = [
  220. 'blat.cc',
  221. ]
  222. os.path.isfile.side_effect = lambda f: 'PRESUBMIT' in f
  223. os.listdir.return_value = [
  224. 'PRESUBMIT.py', 'PRESUBMIT_test.py', 'PRESUBMIT-user.py'
  225. ]
  226. presubmit_files = presubmit.ListRelevantPresubmitFiles(
  227. files, self.fake_root_dir)
  228. self.assertEqual(presubmit_files, [
  229. os.path.join(self.fake_root_dir, 'PRESUBMIT.py'),
  230. os.path.join(self.fake_root_dir, 'PRESUBMIT-user.py'),
  231. ])
  232. def testListRelevantPresubmitFilesInheritSettings(self):
  233. sys_root_dir = self._OS_SEP
  234. root_dir = os.path.join(sys_root_dir, 'foo', 'bar')
  235. inherit_path = os.path.join(root_dir, self._INHERIT_SETTINGS)
  236. files = [
  237. 'test.cc',
  238. os.path.join('moo', 'test2.cc'),
  239. os.path.join('zoo', 'test3.cc')
  240. ]
  241. known_files = [
  242. inherit_path,
  243. os.path.join(sys_root_dir, 'foo', 'PRESUBMIT.py'),
  244. os.path.join(sys_root_dir, 'foo', 'bar', 'moo', 'PRESUBMIT.py'),
  245. ]
  246. os.path.isfile.side_effect = lambda f: f in known_files
  247. dirs_with_presubmit = [
  248. os.path.join(sys_root_dir, 'foo'),
  249. os.path.join(sys_root_dir, 'foo', 'bar', 'moo'),
  250. ]
  251. os.listdir.side_effect = (lambda d: ['PRESUBMIT.py']
  252. if d in dirs_with_presubmit else [])
  253. presubmit_files = presubmit.ListRelevantPresubmitFiles(files, root_dir)
  254. self.assertEqual(presubmit_files, [
  255. os.path.join(sys_root_dir, 'foo', 'PRESUBMIT.py'),
  256. os.path.join(sys_root_dir, 'foo', 'bar', 'moo', 'PRESUBMIT.py')
  257. ])
  258. def testTagLineRe(self):
  259. m = presubmit.Change.TAG_LINE_RE.match(' BUG =1223, 1445 \t')
  260. self.assertIsNotNone(m)
  261. self.assertEqual(m.group('key'), 'BUG')
  262. self.assertEqual(m.group('value'), '1223, 1445')
  263. def testGitChange(self):
  264. description_lines = ('Hello there', 'this is a change', 'BUG=123',
  265. 'and some more regular text \t')
  266. unified_diff = [
  267. 'diff --git binary_a.png binary_a.png', 'new file mode 100644',
  268. 'index 0000000..6fbdd6d',
  269. 'Binary files /dev/null and binary_a.png differ',
  270. 'diff --git binary_d.png binary_d.png', 'deleted file mode 100644',
  271. 'index 6fbdd6d..0000000',
  272. 'Binary files binary_d.png and /dev/null differ',
  273. 'diff --git binary_md.png binary_md.png',
  274. 'index 6fbdd6..be3d5d8 100644', 'GIT binary patch', 'delta 109',
  275. 'zcmeyihjs5>)(Opwi4&WXB~yyi6N|G`(i5|?i<2_a@)OH5N{Um`D-<SM@g!_^W9;SR',
  276. 'zO9b*W5{pxTM0slZ=F42indK9U^MTyVQlJ2s%1BMmEKMv1Q^gtS&9nHn&*Ede;|~CU',
  277. 'CMJxLN', '', 'delta 34',
  278. 'scmV+-0Nww+y#@BX1(1W0gkzIp3}CZh0gVZ>`wGVcgW(Rh;SK@ZPa9GXlK=n!', '',
  279. 'diff --git binary_m.png binary_m.png',
  280. 'index 6fbdd6d..be3d5d8 100644',
  281. 'Binary files binary_m.png and binary_m.png differ',
  282. 'diff --git boo/blat.cc boo/blat.cc', 'new file mode 100644',
  283. 'index 0000000..37d18ad', '--- boo/blat.cc', '+++ boo/blat.cc',
  284. '@@ -0,0 +1,5 @@', '+This is some text',
  285. '+which lacks a copyright warning',
  286. '+but it is nonetheless interesting',
  287. '+and worthy of your attention.',
  288. '+Its freshness factor is through the roof.',
  289. 'diff --git floo/delburt.cc floo/delburt.cc',
  290. 'deleted file mode 100644', 'index e06377a..0000000',
  291. '--- floo/delburt.cc', '+++ /dev/null', '@@ -1,14 +0,0 @@',
  292. '-This text used to be here', '-but someone, probably you,',
  293. '-having consumed the text', '- (absorbed its meaning)',
  294. '-decided that it should be made to not exist',
  295. '-that others would not read it.', '- (What happened here?',
  296. '-was the author incompetent?',
  297. '-or is the world today so different from the world',
  298. '- the author foresaw', '-and past imaginination',
  299. '- amounts to rubble, insignificant,',
  300. '-something to be tripped over', '-and frustrated by)',
  301. 'diff --git foo/TestExpectations foo/TestExpectations',
  302. 'index c6e12ab..d1c5f23 100644', '--- foo/TestExpectations',
  303. '+++ foo/TestExpectations', '@@ -1,12 +1,24 @@',
  304. '-Stranger, behold:', '+Strange to behold:', ' This is a text',
  305. ' Its contents existed before.', '', '-It is written:',
  306. '+Weasel words suggest:', ' its contents shall exist after',
  307. ' and its contents', ' with the progress of time',
  308. ' will evolve,', '- snaillike,', '+ erratically,',
  309. ' into still different texts', '-from this.',
  310. '\ No newline at end of file', '+from this.', '+',
  311. '+For the most part,', '+I really think unified diffs',
  312. '+are elegant: the way you can type',
  313. '+diff --git inside/a/text inside/a/text',
  314. '+or something silly like', '+@@ -278,6 +278,10 @@',
  315. '+and have this not be interpreted', '+as the start of a new file',
  316. '+or anything messed up like that,',
  317. '+because you parsed the header', '+correctly.',
  318. '\ No newline at end of file', ''
  319. ]
  320. files = [
  321. ('A ', 'binary_a.png'),
  322. ('D ', 'binary_d.png'),
  323. ('M ', 'binary_m.png'),
  324. ('M ', 'binary_md.png'), # Binary w/ diff
  325. ('A ', 'boo/blat.cc'),
  326. ('D ', 'floo/delburt.cc'),
  327. ('M ', 'foo/TestExpectations')
  328. ]
  329. known_files = [
  330. os.path.join(self.fake_root_dir, *path.split('/'))
  331. for op, path in files if not op.startswith('D')
  332. ]
  333. os.path.isfile.side_effect = lambda f: f in known_files
  334. scm.GIT.GenerateDiff.return_value = '\n'.join(unified_diff)
  335. change = presubmit.GitChange('mychange',
  336. '\n'.join(description_lines),
  337. self.fake_root_dir,
  338. files,
  339. 0,
  340. 0,
  341. None,
  342. upstream='upstream',
  343. end_commit='end_commit')
  344. self.assertIsNotNone(change.Name() == 'mychange')
  345. self.assertIsNotNone(change.DescriptionText(
  346. ) == 'Hello there\nthis is a change\nand some more regular text')
  347. self.assertIsNotNone(
  348. change.FullDescriptionText() == '\n'.join(description_lines))
  349. self.assertIsNotNone(change.BugsFromDescription() == ['123'])
  350. self.assertIsNotNone(len(change.AffectedFiles()) == 7)
  351. self.assertIsNotNone(len(change.AffectedFiles()) == 7)
  352. self.assertIsNotNone(
  353. len(change.AffectedFiles(include_deletes=False)) == 5)
  354. self.assertIsNotNone(
  355. len(change.AffectedFiles(include_deletes=False)) == 5)
  356. # Note that on git, there's no distinction between binary files and text
  357. # files; everything that's not a delete is a text file.
  358. affected_text_files = change.AffectedTestableFiles()
  359. self.assertIsNotNone(len(affected_text_files) == 5)
  360. local_paths = change.LocalPaths()
  361. expected_paths = [os.path.normpath(f) for op, f in files]
  362. self.assertEqual(local_paths, expected_paths)
  363. actual_rhs_lines = []
  364. for f, linenum, line in change.RightHandSideLines():
  365. actual_rhs_lines.append((f.LocalPath(), linenum, line))
  366. scm.GIT.GenerateDiff.assert_called_once_with(self.fake_root_dir,
  367. files=[],
  368. full_move=True,
  369. branch='upstream',
  370. branch_head='end_commit')
  371. f_blat = os.path.normpath('boo/blat.cc')
  372. f_test_expectations = os.path.normpath('foo/TestExpectations')
  373. expected_rhs_lines = [
  374. (f_blat, 1, 'This is some text'),
  375. (f_blat, 2, 'which lacks a copyright warning'),
  376. (f_blat, 3, 'but it is nonetheless interesting'),
  377. (f_blat, 4, 'and worthy of your attention.'),
  378. (f_blat, 5, 'Its freshness factor is through the roof.'),
  379. (f_test_expectations, 1, 'Strange to behold:'),
  380. (f_test_expectations, 5, 'Weasel words suggest:'),
  381. (f_test_expectations, 10, ' erratically,'),
  382. (f_test_expectations, 13, 'from this.'),
  383. (f_test_expectations, 14, ''),
  384. (f_test_expectations, 15, 'For the most part,'),
  385. (f_test_expectations, 16, 'I really think unified diffs'),
  386. (f_test_expectations, 17, 'are elegant: the way you can type'),
  387. (f_test_expectations, 18, 'diff --git inside/a/text inside/a/text'),
  388. (f_test_expectations, 19, 'or something silly like'),
  389. (f_test_expectations, 20, '@@ -278,6 +278,10 @@'),
  390. (f_test_expectations, 21, 'and have this not be interpreted'),
  391. (f_test_expectations, 22, 'as the start of a new file'),
  392. (f_test_expectations, 23, 'or anything messed up like that,'),
  393. (f_test_expectations, 24, 'because you parsed the header'),
  394. (f_test_expectations, 25, 'correctly.')
  395. ]
  396. self.assertEqual(expected_rhs_lines, actual_rhs_lines)
  397. def testInvalidChange(self):
  398. with self.assertRaises(AssertionError):
  399. presubmit.GitChange('mychange',
  400. 'description',
  401. self.fake_root_dir, ['foo/blat.cc', 'bar'],
  402. 0,
  403. 0,
  404. None,
  405. upstream='upstream',
  406. end_commit='HEAD')
  407. def testExecPresubmitScript(self):
  408. description_lines = ('Hello there', 'this is a change', 'BUG=123')
  409. files = [
  410. ['A', 'foo\\blat.cc'],
  411. ]
  412. fake_presubmit = os.path.join(self.fake_root_dir, 'PRESUBMIT.py')
  413. change = presubmit.Change('mychange', '\n'.join(description_lines),
  414. self.fake_root_dir, files, 0, 0, None)
  415. executer = presubmit.PresubmitExecuter(change, False, None,
  416. presubmit.GerritAccessor())
  417. self.assertFalse(executer.ExecPresubmitScript('', fake_presubmit))
  418. # No error if no on-upload entry point
  419. self.assertFalse(
  420. executer.ExecPresubmitScript(
  421. ('def CheckChangeOnCommit(input_api, output_api):\n'
  422. ' return (output_api.PresubmitError("!!"))\n'),
  423. fake_presubmit))
  424. executer = presubmit.PresubmitExecuter(change, True, None,
  425. presubmit.GerritAccessor())
  426. # No error if no on-commit entry point
  427. self.assertFalse(
  428. executer.ExecPresubmitScript(
  429. ('def CheckChangeOnUpload(input_api, output_api):\n'
  430. ' return (output_api.PresubmitError("!!"))\n'),
  431. fake_presubmit))
  432. self.assertFalse(
  433. executer.ExecPresubmitScript(
  434. ('def CheckChangeOnUpload(input_api, output_api):\n'
  435. ' if not input_api.change.BugsFromDescription():\n'
  436. ' return (output_api.PresubmitError("!!"))\n'
  437. ' else:\n'
  438. ' return ()'), fake_presubmit))
  439. self.assertFalse(
  440. executer.ExecPresubmitScript(
  441. 'def CheckChangeOnCommit(input_api, output_api):\n'
  442. ' results = []\n'
  443. ' results.extend(input_api.canned_checks.CheckChangeHasBugField(\n'
  444. ' input_api, output_api))\n'
  445. ' results.extend(input_api.canned_checks.'
  446. 'CheckChangeHasNoUnwantedTags(\n'
  447. ' input_api, output_api))\n'
  448. ' results.extend(input_api.canned_checks.'
  449. 'CheckChangeHasDescription(\n'
  450. ' input_api, output_api))\n'
  451. ' return results\n', fake_presubmit))
  452. def testExecPresubmitScriptWithResultDB(self):
  453. description_lines = ('Hello there', 'this is a change', 'BUG=123')
  454. files = [['A', 'foo\\blat.cc']]
  455. fake_presubmit = os.path.join(self.fake_root_dir, 'PRESUBMIT.py')
  456. change = presubmit.Change('mychange', '\n'.join(description_lines),
  457. self.fake_root_dir, files, 0, 0, None)
  458. executer = presubmit.PresubmitExecuter(change, True, None,
  459. presubmit.GerritAccessor())
  460. sink = self.rdb_client.__enter__.return_value = mock.MagicMock()
  461. # STATUS_PASS on success
  462. executer.ExecPresubmitScript(
  463. 'def CheckChangeOnCommit(input_api, output_api):\n'
  464. ' return [output_api.PresubmitResult("test")]\n', fake_presubmit)
  465. sink.report.assert_called_with('CheckChangeOnCommit',
  466. rdb_wrapper.STATUS_PASS, 0, None)
  467. # STATUS_FAIL on fatal error
  468. sink.reset_mock()
  469. executer.ExecPresubmitScript(
  470. 'def CheckChangeOnCommit(input_api, output_api):\n'
  471. ' return [output_api.PresubmitError("error")]\n', fake_presubmit)
  472. sink.report.assert_called_with('CheckChangeOnCommit',
  473. rdb_wrapper.STATUS_FAIL, 0, "error\n")
  474. def testExecPresubmitScriptTemporaryFilesRemoval(self):
  475. tempfile.NamedTemporaryFile.side_effect = [
  476. MockTemporaryFile('baz'),
  477. MockTemporaryFile('quux'),
  478. ]
  479. fake_presubmit = os.path.join(self.fake_root_dir, 'PRESUBMIT.py')
  480. executer = presubmit.PresubmitExecuter(self.fake_change, False, None,
  481. presubmit.GerritAccessor())
  482. self.assertEqual(
  483. [],
  484. executer.ExecPresubmitScript(
  485. ('def CheckChangeOnUpload(input_api, output_api):\n'
  486. ' if len(input_api._named_temporary_files):\n'
  487. ' return (output_api.PresubmitError("!!"),)\n'
  488. ' return ()\n'), fake_presubmit))
  489. result = executer.ExecPresubmitScript(
  490. ('def CheckChangeOnUpload(input_api, output_api):\n'
  491. ' with input_api.CreateTemporaryFile():\n'
  492. ' pass\n'
  493. ' with input_api.CreateTemporaryFile():\n'
  494. ' pass\n'
  495. ' return [output_api.PresubmitResult(\'\', f)\n'
  496. ' for f in input_api._named_temporary_files]\n'),
  497. fake_presubmit)
  498. self.assertEqual(['baz', 'quux'], [r._items for r in result])
  499. self.assertEqual(os.remove.mock_calls,
  500. [mock.call('baz'), mock.call('quux')])
  501. def testExecPresubmitScriptInSourceDirectory(self):
  502. """Tests that the presubmits are executed with the current working
  503. directory (CWD) set to the directory of the source presubmit script.
  504. """
  505. orig_dir = os.getcwd()
  506. fake_presubmit_dir = os.path.join(self.fake_root_dir, 'fake_dir')
  507. fake_presubmit = os.path.join(fake_presubmit_dir, 'PRESUBMIT.py')
  508. change = self.ExampleChange()
  509. executer = presubmit.PresubmitExecuter(change, False, None,
  510. presubmit.GerritAccessor())
  511. executer.ExecPresubmitScript('', fake_presubmit)
  512. # Check that the executer switched to the directory of the script and
  513. # back.
  514. self.assertEqual(os.chdir.call_args_list, [
  515. mock.call(fake_presubmit_dir),
  516. mock.call(orig_dir),
  517. ])
  518. def testExecPostUploadHookSourceDirectory(self):
  519. """Tests that the post upload hooks are executed with the current working
  520. directory (CWD) set to the directory of the source presubmit script.
  521. s"""
  522. orig_dir = os.getcwd()
  523. fake_presubmit_dir = os.path.join(self.fake_root_dir, 'fake_dir')
  524. fake_presubmit = os.path.join(fake_presubmit_dir, 'PRESUBMIT.py')
  525. change = self.ExampleChange()
  526. executer = presubmit.GetPostUploadExecuter(change,
  527. presubmit.GerritAccessor())
  528. executer.ExecPresubmitScript(self.presubmit_text, fake_presubmit)
  529. # Check that the executer switched to the directory of the script and
  530. # back.
  531. self.assertEqual(os.chdir.call_args_list, [
  532. mock.call(fake_presubmit_dir),
  533. mock.call(orig_dir),
  534. ])
  535. def testDoPostUploadExecuter(self):
  536. os.path.isfile.side_effect = lambda f: 'PRESUBMIT.py' in f
  537. os.listdir.return_value = ['PRESUBMIT.py']
  538. gclient_utils.FileRead.return_value = self.presubmit_text
  539. change = self.ExampleChange()
  540. self.assertEqual(
  541. 0,
  542. presubmit.DoPostUploadExecuter(change=change,
  543. gerrit_obj=None,
  544. verbose=False))
  545. expected = (r'Running post upload checks \.\.\.\n')
  546. self.assertRegexpMatches(sys.stdout.getvalue(), expected)
  547. def testDoPostUploadExecuterWarning(self):
  548. path = os.path.join(self.fake_root_dir, 'PRESUBMIT.py')
  549. os.path.isfile.side_effect = lambda f: f == path
  550. os.listdir.return_value = ['PRESUBMIT.py']
  551. gclient_utils.FileRead.return_value = self.presubmit_text
  552. change = self.ExampleChange(extra_lines=['PROMPT_WARNING=yes'])
  553. self.assertEqual(
  554. 0,
  555. presubmit.DoPostUploadExecuter(change=change,
  556. gerrit_obj=None,
  557. verbose=False))
  558. self.assertEqual(
  559. 'Running post upload checks ...\n'
  560. '\n'
  561. '** Post Upload Hook Messages **\n'
  562. '??\n'
  563. '\n', sys.stdout.getvalue())
  564. def testDoPostUploadExecuterWarning(self):
  565. path = os.path.join(self.fake_root_dir, 'PRESUBMIT.py')
  566. os.path.isfile.side_effect = lambda f: f == path
  567. os.listdir.return_value = ['PRESUBMIT.py']
  568. gclient_utils.FileRead.return_value = self.presubmit_text
  569. change = self.ExampleChange(extra_lines=['ERROR=yes'])
  570. self.assertEqual(
  571. 1,
  572. presubmit.DoPostUploadExecuter(change=change,
  573. gerrit_obj=None,
  574. verbose=False))
  575. expected = ('Running post upload checks \.\.\.\n'
  576. '\n'
  577. '\*\* Post Upload Hook Messages \*\*\n'
  578. '!!\n'
  579. '\n')
  580. self.assertRegexpMatches(sys.stdout.getvalue(), expected)
  581. def testDoPresubmitChecksNoWarningsOrErrors(self):
  582. haspresubmit_path = os.path.join(self.fake_root_dir, 'haspresubmit',
  583. 'PRESUBMIT.py')
  584. root_path = os.path.join(self.fake_root_dir, 'PRESUBMIT.py')
  585. os.path.isfile.side_effect = lambda f: f in [
  586. root_path, haspresubmit_path
  587. ]
  588. os.listdir.return_value = ['PRESUBMIT.py']
  589. gclient_utils.FileRead.return_value = self.presubmit_text
  590. # Make a change which will have no warnings.
  591. change = self.ExampleChange(extra_lines=['STORY=http://tracker/123'])
  592. self.assertEqual(
  593. 0,
  594. presubmit.DoPresubmitChecks(change=change,
  595. committing=False,
  596. verbose=True,
  597. default_presubmit=None,
  598. may_prompt=False,
  599. gerrit_obj=None,
  600. json_output=None))
  601. self.assertEqual(sys.stdout.getvalue().count('!!'), 0)
  602. self.assertEqual(sys.stdout.getvalue().count('??'), 0)
  603. self.assertEqual(sys.stdout.getvalue().count(RUNNING_PY_CHECKS_TEXT), 1)
  604. def testDoPresubmitChecksJsonOutput(self):
  605. fake_error = 'Missing LGTM'
  606. fake_error_items = '["!", "!!", "!!!"]'
  607. fake_error_long_text = "Error long text..."
  608. fake_error_locations = '[output_api.PresubmitResultLocation(file_path="path/to/file")]'
  609. fake_error2 = 'This failed was found in file fake.py'
  610. fake_error2_items = '["!!!", "!!", "!"]'
  611. fake_error2_long_text = " Error long text" * 3
  612. fake_warning = 'Line 88 is more than 80 characters.'
  613. fake_warning_items = '["W", "w"]'
  614. fake_warning_long_text = 'Warning long text...'
  615. fake_warning_locations = (
  616. '['
  617. 'output_api.PresubmitResultLocation(file_path="path/to/foo", start_line=1, end_line=1), '
  618. 'output_api.PresubmitResultLocation(file_path="path/to/bar", start_line=4, start_col=5, end_line=6, end_col=7)'
  619. ']')
  620. fake_notify = 'This is a dry run'
  621. fake_notify_items = '["N"]'
  622. fake_notify_long_text = 'Notification long text...'
  623. always_fail_presubmit_script = (f"""\n
  624. def CheckChangeOnUpload(input_api, output_api):
  625. output_api.more_cc = ['me@example.com']
  626. return [
  627. output_api.PresubmitError("{fake_error}", {fake_error_items}, "{fake_error_long_text}", {fake_error_locations}),
  628. output_api.PresubmitError("{fake_error2}", {fake_error2_items}, "{fake_error2_long_text}"),
  629. output_api.PresubmitPromptWarning("{fake_warning}", {fake_warning_items}, "{fake_warning_long_text}", {fake_warning_locations}),
  630. output_api.PresubmitNotifyResult("{fake_notify}", {fake_notify_items}, "{fake_notify_long_text}")
  631. ]
  632. def CheckChangeOnCommit(input_api, output_api):
  633. raise Exception("Test error")
  634. """)
  635. os.path.isfile.return_value = False
  636. os.listdir.side_effect = [[], ['PRESUBMIT.py']]
  637. random.randint.return_value = 0
  638. change = self.ExampleChange(extra_lines=['ERROR=yes'])
  639. temp_path = 'temp.json'
  640. fake_result = {
  641. 'notifications': [{
  642. 'message': fake_notify,
  643. 'items': json.loads(fake_notify_items),
  644. 'fatal': False,
  645. 'long_text': fake_notify_long_text,
  646. 'locations': [],
  647. }],
  648. 'errors': [{
  649. 'message':
  650. fake_error,
  651. 'items':
  652. json.loads(fake_error_items),
  653. 'fatal':
  654. True,
  655. 'long_text':
  656. fake_error_long_text,
  657. 'locations': [{
  658. 'file_path': 'path/to/file',
  659. 'start_line': 0,
  660. 'start_col': 0,
  661. 'end_line': 0,
  662. 'end_col': 0,
  663. }],
  664. }, {
  665. 'message': fake_error2,
  666. 'items': json.loads(fake_error2_items),
  667. 'fatal': True,
  668. 'long_text': fake_error2_long_text,
  669. 'locations': [],
  670. }],
  671. 'warnings': [{
  672. 'message':
  673. fake_warning,
  674. 'items':
  675. json.loads(fake_warning_items),
  676. 'fatal':
  677. False,
  678. 'long_text':
  679. fake_warning_long_text,
  680. 'locations': [{
  681. 'file_path': 'path/to/foo',
  682. 'start_line': 1,
  683. 'start_col': 0,
  684. 'end_line': 1,
  685. 'end_col': 0,
  686. }, {
  687. 'file_path': 'path/to/bar',
  688. 'start_line': 4,
  689. 'start_col': 5,
  690. 'end_line': 6,
  691. 'end_col': 7,
  692. }],
  693. }],
  694. 'more_cc': ['me@example.com'],
  695. }
  696. fake_result_json = json.dumps(fake_result, sort_keys=True)
  697. self.assertEqual(
  698. 1,
  699. presubmit.DoPresubmitChecks(
  700. change=change,
  701. committing=False,
  702. verbose=True,
  703. default_presubmit=always_fail_presubmit_script,
  704. may_prompt=False,
  705. gerrit_obj=None,
  706. json_output=temp_path))
  707. gclient_utils.FileWrite.assert_called_with(temp_path, fake_result_json)
  708. def testDoPresubmitChecksPromptsAfterWarnings(self):
  709. presubmit_path = os.path.join(self.fake_root_dir, 'PRESUBMIT.py')
  710. haspresubmit_path = os.path.join(self.fake_root_dir, 'haspresubmit',
  711. 'PRESUBMIT.py')
  712. os.path.isfile.side_effect = (
  713. lambda f: f in [presubmit_path, haspresubmit_path])
  714. os.listdir.return_value = ['PRESUBMIT.py']
  715. random.randint.return_value = 1
  716. gclient_utils.FileRead.return_value = self.presubmit_text
  717. # Make a change with a single warning.
  718. change = self.ExampleChange(extra_lines=['PROMPT_WARNING=yes'])
  719. # say no to the warning
  720. with mock.patch('sys.stdin', StringIO('n\n')):
  721. self.assertEqual(
  722. 1,
  723. presubmit.DoPresubmitChecks(change=change,
  724. committing=False,
  725. verbose=True,
  726. default_presubmit=None,
  727. may_prompt=True,
  728. gerrit_obj=None,
  729. json_output=None))
  730. self.assertEqual(sys.stdout.getvalue().count('??'), 2)
  731. sys.stdout.truncate(0)
  732. # say yes to the warning
  733. with mock.patch('sys.stdin', StringIO('y\n')):
  734. self.assertEqual(
  735. 0,
  736. presubmit.DoPresubmitChecks(change=change,
  737. committing=False,
  738. verbose=True,
  739. default_presubmit=None,
  740. may_prompt=True,
  741. gerrit_obj=None,
  742. json_output=None))
  743. self.assertEqual(sys.stdout.getvalue().count('??'), 2)
  744. self.assertEqual(sys.stdout.getvalue().count(RUNNING_PY_CHECKS_TEXT), 1)
  745. def testDoPresubmitChecksWithWarningsAndNoPrompt(self):
  746. presubmit_path = os.path.join(self.fake_root_dir, 'PRESUBMIT.py')
  747. haspresubmit_path = os.path.join(self.fake_root_dir, 'haspresubmit',
  748. 'PRESUBMIT.py')
  749. os.path.isfile.side_effect = (
  750. lambda f: f in [presubmit_path, haspresubmit_path])
  751. os.listdir.return_value = ['PRESUBMIT.py']
  752. gclient_utils.FileRead.return_value = self.presubmit_text
  753. random.randint.return_value = 1
  754. change = self.ExampleChange(extra_lines=['PROMPT_WARNING=yes'])
  755. # There is no input buffer and may_prompt is set to False.
  756. self.assertEqual(
  757. 0,
  758. presubmit.DoPresubmitChecks(change=change,
  759. committing=False,
  760. verbose=True,
  761. default_presubmit=None,
  762. may_prompt=False,
  763. gerrit_obj=None,
  764. json_output=None))
  765. # A warning is printed, and should_continue is True.
  766. self.assertEqual(sys.stdout.getvalue().count('??'), 2)
  767. self.assertEqual(sys.stdout.getvalue().count('(y/N)'), 0)
  768. self.assertEqual(sys.stdout.getvalue().count(RUNNING_PY_CHECKS_TEXT), 1)
  769. def testDoPresubmitChecksNoWarningPromptIfErrors(self):
  770. presubmit_path = os.path.join(self.fake_root_dir, 'PRESUBMIT.py')
  771. haspresubmit_path = os.path.join(self.fake_root_dir, 'haspresubmit',
  772. 'PRESUBMIT.py')
  773. os.path.isfile.side_effect = (
  774. lambda f: f in [presubmit_path, haspresubmit_path])
  775. os.listdir.return_value = ['PRESUBMIT.py']
  776. gclient_utils.FileRead.return_value = self.presubmit_text
  777. random.randint.return_value = 1
  778. change = self.ExampleChange(extra_lines=['ERROR=yes'])
  779. self.assertEqual(
  780. 1,
  781. presubmit.DoPresubmitChecks(change=change,
  782. committing=False,
  783. verbose=True,
  784. default_presubmit=None,
  785. may_prompt=True,
  786. gerrit_obj=None,
  787. json_output=None))
  788. self.assertEqual(sys.stdout.getvalue().count('??'), 0)
  789. self.assertEqual(sys.stdout.getvalue().count('!!'), 2)
  790. self.assertEqual(sys.stdout.getvalue().count('(y/N)'), 0)
  791. self.assertEqual(sys.stdout.getvalue().count(RUNNING_PY_CHECKS_TEXT), 1)
  792. def testDoDefaultPresubmitChecksAndFeedback(self):
  793. always_fail_presubmit_script = ("""\n
  794. def CheckChangeOnUpload(input_api, output_api):
  795. return [output_api.PresubmitError("!!")]
  796. def CheckChangeOnCommit(input_api, output_api):
  797. raise Exception("Test error")
  798. """)
  799. os.path.isfile.return_value = False
  800. os.listdir.side_effect = (
  801. lambda d: [] if d == self.fake_root_dir else ['PRESUBMIT.py'])
  802. random.randint.return_value = 0
  803. change = self.ExampleChange(extra_lines=['STORY=http://tracker/123'])
  804. with mock.patch('sys.stdin', StringIO('y\n')):
  805. self.assertEqual(
  806. 1,
  807. presubmit.DoPresubmitChecks(
  808. change=change,
  809. committing=False,
  810. verbose=True,
  811. default_presubmit=always_fail_presubmit_script,
  812. may_prompt=False,
  813. gerrit_obj=None,
  814. json_output=None))
  815. text = (
  816. RUNNING_PY_CHECKS_TEXT + 'Warning, no PRESUBMIT.py found.\n'
  817. 'Running default presubmit script.\n'
  818. '** Presubmit ERRORS: 1 **\n!!\n\n'
  819. 'There were presubmit errors.\n'
  820. 'Was the presubmit check useful? If not, run "git cl presubmit -v"\n'
  821. 'to figure out which PRESUBMIT.py was run, then run "git blame"\n'
  822. 'on the file to figure out who to ask for help.\n')
  823. self.assertEqual(sys.stdout.getvalue(), text)
  824. def ExampleChange(self, extra_lines=None):
  825. """Returns an example Change instance for tests."""
  826. description_lines = [
  827. 'Hello there',
  828. 'This is a change',
  829. ] + (extra_lines or [])
  830. files = [
  831. ['A', os.path.join('haspresubmit', 'blat.cc')],
  832. ]
  833. return presubmit.Change(name='mychange',
  834. description='\n'.join(description_lines),
  835. local_root=self.fake_root_dir,
  836. files=files,
  837. issue=0,
  838. patchset=0,
  839. author=None)
  840. def testMainPostUpload(self):
  841. os.path.isfile.side_effect = lambda f: 'PRESUBMIT.py' in f
  842. os.listdir.return_value = ['PRESUBMIT.py']
  843. gclient_utils.FileRead.return_value = (
  844. 'def PostUploadHook(gerrit, change, output_api):\n'
  845. ' return ()\n')
  846. scm.determine_scm.return_value = None
  847. presubmit._parse_files.return_value = [('M', 'random_file.txt')]
  848. self.assertEqual(
  849. 0,
  850. presubmit.main([
  851. '--root', self.fake_root_dir, 'random_file.txt', '--post_upload'
  852. ]))
  853. @mock.patch('presubmit_support.ListRelevantPresubmitFiles')
  854. def testMainUnversioned(self, *_mocks):
  855. gclient_utils.FileRead.return_value = ''
  856. scm.determine_scm.return_value = None
  857. presubmit.ListRelevantPresubmitFiles.return_value = [
  858. os.path.join(self.fake_root_dir, 'PRESUBMIT.py')
  859. ]
  860. self.assertEqual(
  861. 0, presubmit.main(['--root', self.fake_root_dir,
  862. 'random_file.txt']))
  863. @mock.patch('presubmit_support.ListRelevantPresubmitFiles')
  864. def testMainUnversionedChecksFail(self, *_mocks):
  865. gclient_utils.FileRead.return_value = (
  866. 'def CheckChangeOnUpload(input_api, output_api):\n'
  867. ' return [output_api.PresubmitError("!!")]\n')
  868. scm.determine_scm.return_value = None
  869. presubmit.ListRelevantPresubmitFiles.return_value = [
  870. os.path.join(self.fake_root_dir, 'PRESUBMIT.py')
  871. ]
  872. self.assertEqual(
  873. 1, presubmit.main(['--root', self.fake_root_dir,
  874. 'random_file.txt']))
  875. def testMainUnversionedFail(self):
  876. scm.determine_scm.return_value = 'diff'
  877. with self.assertRaises(SystemExit) as e:
  878. presubmit.main(['--root', self.fake_root_dir])
  879. self.assertEqual(2, e.exception.code)
  880. self.assertEqual(
  881. sys.stderr.getvalue(),
  882. 'usage: presubmit_unittest.py [options] <files...>\n'
  883. 'presubmit_unittest.py: error: unversioned directories must '
  884. 'specify <files>, <all_files>, or <diff_file>.\n')
  885. @mock.patch('presubmit_support.Change', mock.Mock())
  886. def testParseChange_Files(self):
  887. presubmit._parse_files.return_value = [('M', 'random_file.txt')]
  888. scm.determine_scm.return_value = None
  889. options = mock.Mock(all_files=False,
  890. generate_diff=False,
  891. source_controlled_only=False)
  892. change = presubmit._parse_change(None, options)
  893. self.assertEqual(presubmit.Change.return_value, change)
  894. presubmit.Change.assert_called_once_with(
  895. options.name, options.description, options.root,
  896. [('M', 'random_file.txt')], options.issue, options.patchset,
  897. options.author)
  898. presubmit._parse_files.assert_called_once_with(options.files,
  899. options.recursive)
  900. def testParseChange_NoFilesAndDiff(self):
  901. presubmit._parse_files.return_value = []
  902. scm.determine_scm.return_value = 'diff'
  903. parser = mock.Mock()
  904. parser.error.side_effect = [SystemExit]
  905. options = mock.Mock(files=[], diff_file='', all_files=False)
  906. with self.assertRaises(SystemExit):
  907. presubmit._parse_change(parser, options)
  908. parser.error.assert_called_once_with(
  909. 'unversioned directories must specify '
  910. '<files>, <all_files>, or <diff_file>.')
  911. def testParseChange_FilesAndAllFiles(self):
  912. parser = mock.Mock()
  913. parser.error.side_effect = [SystemExit]
  914. options = mock.Mock(files=['foo'], all_files=True)
  915. with self.assertRaises(SystemExit):
  916. presubmit._parse_change(parser, options)
  917. parser.error.assert_called_once_with(
  918. '<files> cannot be specified when --all-files is set.')
  919. def testParseChange_DiffAndAllFiles(self):
  920. parser = mock.Mock()
  921. parser.error.side_effect = [SystemExit]
  922. options = mock.Mock(files=[], all_files=True, diff_file='foo.diff')
  923. with self.assertRaises(SystemExit):
  924. presubmit._parse_change(parser, options)
  925. parser.error.assert_called_once_with(
  926. '<diff_file> cannot be specified when --all-files is set.')
  927. def testParseChange_DiffAndGenerateDiff(self):
  928. parser = mock.Mock()
  929. parser.error.side_effect = [SystemExit]
  930. options = mock.Mock(all_files=False,
  931. files=[],
  932. generate_diff=True,
  933. diff_file='foo.diff')
  934. with self.assertRaises(SystemExit):
  935. presubmit._parse_change(parser, options)
  936. parser.error.assert_called_once_with(
  937. '<diff_file> cannot be specified when <generate_diff> is set.')
  938. @mock.patch('presubmit_support.GitChange', mock.Mock())
  939. def testParseChange_FilesAndGit(self):
  940. scm.determine_scm.return_value = 'git'
  941. presubmit._parse_files.return_value = [('M', 'random_file.txt')]
  942. options = mock.Mock(all_files=False,
  943. generate_diff=False,
  944. source_controlled_only=False)
  945. change = presubmit._parse_change(None, options)
  946. self.assertEqual(presubmit.GitChange.return_value, change)
  947. presubmit.GitChange.assert_called_once_with(
  948. options.name,
  949. options.description,
  950. options.root, [('M', 'random_file.txt')],
  951. options.issue,
  952. options.patchset,
  953. options.author,
  954. upstream=options.upstream,
  955. end_commit=options.end_commit)
  956. presubmit._parse_files.assert_called_once_with(options.files,
  957. options.recursive)
  958. @mock.patch('presubmit_support.GitChange', mock.Mock())
  959. @mock.patch('scm.GIT.CaptureStatus', mock.Mock())
  960. def testParseChange_NoFilesAndGit(self):
  961. scm.determine_scm.return_value = 'git'
  962. scm.GIT.CaptureStatus.return_value = [('A', 'added.txt')]
  963. options = mock.Mock(all_files=False, files=[], diff_file='')
  964. change = presubmit._parse_change(None, options)
  965. self.assertEqual(presubmit.GitChange.return_value, change)
  966. presubmit.GitChange.assert_called_once_with(
  967. options.name,
  968. options.description,
  969. options.root, [('A', 'added.txt')],
  970. options.issue,
  971. options.patchset,
  972. options.author,
  973. upstream=options.upstream,
  974. end_commit=options.end_commit)
  975. scm.GIT.CaptureStatus.assert_called_once_with(options.root,
  976. options.upstream,
  977. ignore_submodules=False)
  978. @mock.patch('presubmit_support.GitChange', mock.Mock())
  979. @mock.patch('scm.GIT.GetAllFiles', mock.Mock())
  980. def testParseChange_AllFilesAndGit(self):
  981. scm.determine_scm.return_value = 'git'
  982. scm.GIT.GetAllFiles.return_value = ['foo.txt', 'bar.txt']
  983. options = mock.Mock(all_files=True, files=[], diff_file='')
  984. change = presubmit._parse_change(None, options)
  985. self.assertEqual(presubmit.GitChange.return_value, change)
  986. presubmit.GitChange.assert_called_once_with(
  987. options.name,
  988. options.description,
  989. options.root, [('M', 'foo.txt'), ('M', 'bar.txt')],
  990. options.issue,
  991. options.patchset,
  992. options.author,
  993. upstream=options.upstream,
  994. end_commit=options.end_commit)
  995. scm.GIT.GetAllFiles.assert_called_once_with(options.root)
  996. @mock.patch('presubmit_support.ProvidedDiffChange', mock.Mock())
  997. def testParseChange_ProvidedDiffFile(self):
  998. diff = """
  999. diff --git a/foo b/foo
  1000. new file mode 100644
  1001. index 0000000..9daeafb
  1002. --- /dev/null
  1003. +++ b/foo
  1004. @@ -0,0 +1 @@
  1005. +test
  1006. diff --git a/bar b/bar
  1007. deleted file mode 100644
  1008. index f675c2a..0000000
  1009. --- a/bar
  1010. +++ /dev/null
  1011. @@ -1,1 +0,0 @@
  1012. -bar
  1013. diff --git a/baz b/baz
  1014. index d7ba659f..b7957f3 100644
  1015. --- a/baz
  1016. +++ b/baz
  1017. @@ -1,1 +1,1 @@
  1018. -baz
  1019. +bat"""
  1020. gclient_utils.FileRead.return_value = diff
  1021. options = mock.Mock(all_files=False,
  1022. files=[],
  1023. generate_diff=False,
  1024. diff_file='foo.diff')
  1025. change = presubmit._parse_change(None, options)
  1026. self.assertEqual(presubmit.ProvidedDiffChange.return_value, change)
  1027. presubmit.ProvidedDiffChange.assert_called_once_with(
  1028. options.name,
  1029. options.description,
  1030. options.root, [('A', 'foo'), ('D', 'bar'), ('M', 'baz')],
  1031. options.issue,
  1032. options.patchset,
  1033. options.author,
  1034. diff=diff)
  1035. def testParseGerritOptions_NoGerritUrl(self):
  1036. options = mock.Mock(gerrit_url=None,
  1037. gerrit_fetch=False,
  1038. author='author',
  1039. description='description')
  1040. gerrit_obj = presubmit._parse_gerrit_options(None, options)
  1041. self.assertIsNone(gerrit_obj)
  1042. self.assertEqual('author', options.author)
  1043. self.assertEqual('description', options.description)
  1044. def testParseGerritOptions_NoGerritFetch(self):
  1045. options = mock.Mock(
  1046. gerrit_url='https://foo-review.googlesource.com/bar',
  1047. gerrit_project='project',
  1048. gerrit_branch='refs/heads/main',
  1049. gerrit_fetch=False,
  1050. author='author',
  1051. description='description')
  1052. gerrit_obj = presubmit._parse_gerrit_options(None, options)
  1053. self.assertEqual('foo-review.googlesource.com', gerrit_obj.host)
  1054. self.assertEqual('project', gerrit_obj.project)
  1055. self.assertEqual('refs/heads/main', gerrit_obj.branch)
  1056. self.assertEqual('author', options.author)
  1057. self.assertEqual('description', options.description)
  1058. @mock.patch('presubmit_support.GerritAccessor.GetChangeOwner')
  1059. @mock.patch('presubmit_support.GerritAccessor.GetChangeDescription')
  1060. def testParseGerritOptions_GerritFetch(self, mockDescription, mockOwner):
  1061. mockDescription.return_value = 'new description'
  1062. mockOwner.return_value = 'new owner'
  1063. options = mock.Mock(
  1064. gerrit_url='https://foo-review.googlesource.com/bar',
  1065. gerrit_project='project',
  1066. gerrit_branch='refs/heads/main',
  1067. gerrit_fetch=True,
  1068. issue=123,
  1069. patchset=4)
  1070. gerrit_obj = presubmit._parse_gerrit_options(None, options)
  1071. self.assertEqual('foo-review.googlesource.com', gerrit_obj.host)
  1072. self.assertEqual('project', gerrit_obj.project)
  1073. self.assertEqual('refs/heads/main', gerrit_obj.branch)
  1074. self.assertEqual('new owner', options.author)
  1075. self.assertEqual('new description', options.description)
  1076. def testParseGerritOptions_GerritFetchNoUrl(self):
  1077. parser = mock.Mock()
  1078. parser.error.side_effect = [SystemExit]
  1079. options = mock.Mock(gerrit_url=None,
  1080. gerrit_fetch=True,
  1081. issue=123,
  1082. patchset=4)
  1083. with self.assertRaises(SystemExit):
  1084. presubmit._parse_gerrit_options(parser, options)
  1085. parser.error.assert_called_once_with(
  1086. '--gerrit_fetch requires --gerrit_url, --issue and --patchset.')
  1087. def testParseGerritOptions_GerritFetchNoIssue(self):
  1088. parser = mock.Mock()
  1089. parser.error.side_effect = [SystemExit]
  1090. options = mock.Mock(gerrit_url='https://example.com',
  1091. gerrit_fetch=True,
  1092. issue=None,
  1093. patchset=4)
  1094. with self.assertRaises(SystemExit):
  1095. presubmit._parse_gerrit_options(parser, options)
  1096. parser.error.assert_called_once_with(
  1097. '--gerrit_fetch requires --gerrit_url, --issue and --patchset.')
  1098. def testParseGerritOptions_GerritFetchNoPatchset(self):
  1099. parser = mock.Mock()
  1100. parser.error.side_effect = [SystemExit]
  1101. options = mock.Mock(gerrit_url='https://example.com',
  1102. gerrit_fetch=True,
  1103. issue=123,
  1104. patchset=None)
  1105. with self.assertRaises(SystemExit):
  1106. presubmit._parse_gerrit_options(parser, options)
  1107. parser.error.assert_called_once_with(
  1108. '--gerrit_fetch requires --gerrit_url, --issue and --patchset.')
  1109. class InputApiUnittest(PresubmitTestsBase):
  1110. """Tests presubmit.InputApi."""
  1111. def testInputApiConstruction(self):
  1112. api = presubmit.InputApi(self.fake_change,
  1113. presubmit_path='foo/path/PRESUBMIT.py',
  1114. is_committing=False,
  1115. gerrit_obj=None,
  1116. verbose=False)
  1117. self.assertEqual(api.PresubmitLocalPath(), 'foo/path')
  1118. self.assertEqual(api.change, self.fake_change)
  1119. def testInputApiPresubmitScriptFiltering(self):
  1120. description_lines = ('Hello there', 'this is a change', 'BUG=123',
  1121. ' STORY =http://foo/ \t',
  1122. 'and some more regular text')
  1123. files = [
  1124. ['A', os.path.join('foo', 'blat.cc'), True],
  1125. ['M', os.path.join('foo', 'blat', 'READ_ME2'), True],
  1126. ['M', os.path.join('foo', 'blat', 'binary.dll'), True],
  1127. ['M', os.path.join('foo', 'blat', 'weird.xyz'), True],
  1128. ['M', os.path.join('foo', 'blat', 'another.h'), True],
  1129. ['M', os.path.join('foo', 'third_party', 'third.cc'), True],
  1130. ['D', os.path.join('foo', 'mat', 'beingdeleted.txt'), False],
  1131. ['M', os.path.join('flop', 'notfound.txt'), False],
  1132. ['A', os.path.join('boo', 'flap.h'), True],
  1133. ]
  1134. diffs = []
  1135. known_files = []
  1136. for _, f, exists in files:
  1137. full_file = os.path.join(self.fake_root_dir, f)
  1138. if exists and f.startswith('foo'):
  1139. known_files.append(full_file)
  1140. diffs.append(self.presubmit_diffs % {'filename': f})
  1141. os.path.isfile.side_effect = lambda f: f in known_files
  1142. presubmit.scm.GIT.GenerateDiff.return_value = '\n'.join(diffs)
  1143. change = presubmit.GitChange('mychange',
  1144. '\n'.join(description_lines),
  1145. self.fake_root_dir,
  1146. [[f[0], f[1]] for f in files],
  1147. 0,
  1148. 0,
  1149. None,
  1150. upstream='upstream',
  1151. end_commit='HEAD')
  1152. input_api = presubmit.InputApi(
  1153. change, os.path.join(self.fake_root_dir, 'foo', 'PRESUBMIT.py'),
  1154. False, None, False)
  1155. # Doesn't filter much
  1156. got_files = input_api.AffectedFiles()
  1157. self.assertEqual(len(got_files), 7)
  1158. self.assertEqual(got_files[0].LocalPath(),
  1159. presubmit.normpath(files[0][1]))
  1160. self.assertEqual(got_files[1].LocalPath(),
  1161. presubmit.normpath(files[1][1]))
  1162. self.assertEqual(got_files[2].LocalPath(),
  1163. presubmit.normpath(files[2][1]))
  1164. self.assertEqual(got_files[3].LocalPath(),
  1165. presubmit.normpath(files[3][1]))
  1166. self.assertEqual(got_files[4].LocalPath(),
  1167. presubmit.normpath(files[4][1]))
  1168. self.assertEqual(got_files[5].LocalPath(),
  1169. presubmit.normpath(files[5][1]))
  1170. self.assertEqual(got_files[6].LocalPath(),
  1171. presubmit.normpath(files[6][1]))
  1172. # Ignores weird because of check_list, third_party because of skip_list,
  1173. # binary isn't a text file and being deleted doesn't exist. The rest is
  1174. # outside foo/.
  1175. rhs_lines = list(input_api.RightHandSideLines(None))
  1176. self.assertEqual(len(rhs_lines), 14)
  1177. self.assertEqual(rhs_lines[0][0].LocalPath(),
  1178. presubmit.normpath(files[0][1]))
  1179. self.assertEqual(rhs_lines[3][0].LocalPath(),
  1180. presubmit.normpath(files[0][1]))
  1181. self.assertEqual(rhs_lines[7][0].LocalPath(),
  1182. presubmit.normpath(files[4][1]))
  1183. self.assertEqual(rhs_lines[13][0].LocalPath(),
  1184. presubmit.normpath(files[4][1]))
  1185. def testInputApiFilterSourceFile(self):
  1186. files = [
  1187. ['A', os.path.join('foo', 'blat.cc')],
  1188. ['M', os.path.join('foo', 'blat', 'READ_ME2')],
  1189. ['M', os.path.join('foo', 'blat', 'binary.dll')],
  1190. ['M', os.path.join('foo', 'blat', 'weird.xyz')],
  1191. ['M', os.path.join('foo', 'blat', 'another.h')],
  1192. ['M',
  1193. os.path.join('foo', 'third_party', 'WebKit', 'WebKit.cpp')],
  1194. ['M',
  1195. os.path.join('foo', 'third_party', 'WebKit2', 'WebKit2.cpp')],
  1196. ['M', os.path.join('foo', 'third_party', 'blink', 'blink.cc')],
  1197. ['M',
  1198. os.path.join('foo', 'third_party', 'blink1', 'blink1.cc')],
  1199. ['M', os.path.join('foo', 'third_party', 'third', 'third.cc')],
  1200. ]
  1201. known_files = [os.path.join(self.fake_root_dir, f) for _, f in files]
  1202. os.path.isfile.side_effect = lambda f: f in known_files
  1203. change = presubmit.GitChange('mychange',
  1204. 'description\nlines\n',
  1205. self.fake_root_dir,
  1206. [[f[0], f[1]] for f in files],
  1207. 0,
  1208. 0,
  1209. None,
  1210. upstream='upstream',
  1211. end_commit='HEAD')
  1212. input_api = presubmit.InputApi(
  1213. change, os.path.join(self.fake_root_dir, 'foo', 'PRESUBMIT.py'),
  1214. False, None, False)
  1215. # We'd like to test FilterSourceFile, which is used by
  1216. # AffectedSourceFiles(None).
  1217. got_files = input_api.AffectedSourceFiles(None)
  1218. self.assertEqual(len(got_files), 4)
  1219. # blat.cc, another.h, WebKit.cpp, and blink.cc remain.
  1220. self.assertEqual(got_files[0].LocalPath(),
  1221. presubmit.normpath(files[0][1]))
  1222. self.assertEqual(got_files[1].LocalPath(),
  1223. presubmit.normpath(files[4][1]))
  1224. self.assertEqual(got_files[2].LocalPath(),
  1225. presubmit.normpath(files[5][1]))
  1226. self.assertEqual(got_files[3].LocalPath(),
  1227. presubmit.normpath(files[7][1]))
  1228. self.assertIn('foo/blat.cc', input_api.UnixLocalPaths())
  1229. if sys.platform == 'win32':
  1230. self.assertIn(r'foo\blat.cc', input_api.LocalPaths())
  1231. else:
  1232. self.assertIn('foo/blat.cc', input_api.LocalPaths())
  1233. def testDefaultFilesToCheckFilesToSkipFilters(self):
  1234. def f(x):
  1235. return presubmit.AffectedFile(x, 'M', self.fake_root_dir, None)
  1236. files = [
  1237. (
  1238. [
  1239. # To be tested.
  1240. f('testing_support/google_appengine/b'),
  1241. f('testing_support/not_google_appengine/foo.cc'),
  1242. ],
  1243. [
  1244. # Expected.
  1245. 'testing_support/not_google_appengine/foo.cc',
  1246. ],
  1247. ),
  1248. (
  1249. [
  1250. # To be tested.
  1251. f('a/experimental/b'),
  1252. f('experimental/b'),
  1253. f('a/experimental'),
  1254. f('a/experimental.cc'),
  1255. f('a/experimental.S'),
  1256. ],
  1257. [
  1258. # Expected.
  1259. 'a/experimental.cc',
  1260. 'a/experimental.S',
  1261. ],
  1262. ),
  1263. (
  1264. [
  1265. # To be tested.
  1266. f('a/third_party/b'),
  1267. f('third_party/b'),
  1268. f('a/third_party'),
  1269. f('a/third_party.cc'),
  1270. ],
  1271. [
  1272. # Expected.
  1273. 'a/third_party.cc',
  1274. ],
  1275. ),
  1276. (
  1277. [
  1278. # To be tested.
  1279. f('a/LOL_FILE/b'),
  1280. f('b.c/LOL_FILE'),
  1281. f('a/PRESUBMIT.py'),
  1282. f('a/FOO.json'),
  1283. f('a/FOO.java'),
  1284. f('a/FOO.mojom'),
  1285. ],
  1286. [
  1287. # Expected.
  1288. 'a/PRESUBMIT.py',
  1289. 'a/FOO.java',
  1290. 'a/FOO.mojom',
  1291. ],
  1292. ),
  1293. (
  1294. [
  1295. # To be tested.
  1296. f('a/.git'),
  1297. f('b.c/.git'),
  1298. f('a/.git/bleh.py'),
  1299. f('.git/bleh.py'),
  1300. f('bleh.diff'),
  1301. f('foo/bleh.patch'),
  1302. ],
  1303. [
  1304. # Expected.
  1305. ],
  1306. ),
  1307. ]
  1308. input_api = presubmit.InputApi(self.fake_change, './PRESUBMIT.py',
  1309. False, None, False)
  1310. for item in files:
  1311. results = list(filter(input_api.FilterSourceFile, item[0]))
  1312. for i in range(len(results)):
  1313. self.assertEqual(results[i].LocalPath(),
  1314. presubmit.normpath(item[1][i]))
  1315. # Same number of expected results.
  1316. self.assertEqual(sorted([f.UnixLocalPath() for f in results]),
  1317. sorted(item[1]))
  1318. def testDefaultOverrides(self):
  1319. input_api = presubmit.InputApi(self.fake_change, './PRESUBMIT.py',
  1320. False, None, False)
  1321. self.assertEqual(len(input_api.DEFAULT_FILES_TO_CHECK), 27)
  1322. self.assertEqual(len(input_api.DEFAULT_FILES_TO_SKIP), 12)
  1323. input_api.DEFAULT_FILES_TO_CHECK = (r'.+\.c$', )
  1324. input_api.DEFAULT_FILES_TO_SKIP = (r'.+\.patch$', r'.+\.diff')
  1325. self.assertEqual(len(input_api.DEFAULT_FILES_TO_CHECK), 1)
  1326. self.assertEqual(len(input_api.DEFAULT_FILES_TO_SKIP), 2)
  1327. def testCustomFilter(self):
  1328. def FilterSourceFile(affected_file):
  1329. return 'a' in affected_file.LocalPath()
  1330. files = [('A', 'eeaee'), ('M', 'eeabee'), ('M', 'eebcee')]
  1331. known_files = [
  1332. os.path.join(self.fake_root_dir, item) for _, item in files
  1333. ]
  1334. os.path.isfile.side_effect = lambda f: f in known_files
  1335. change = presubmit.GitChange('mychange',
  1336. '',
  1337. self.fake_root_dir,
  1338. files,
  1339. 0,
  1340. 0,
  1341. None,
  1342. upstream='upstream',
  1343. end_commit='HEAD')
  1344. input_api = presubmit.InputApi(
  1345. change, os.path.join(self.fake_root_dir, 'PRESUBMIT.py'), False,
  1346. None, False)
  1347. got_files = input_api.AffectedSourceFiles(FilterSourceFile)
  1348. self.assertEqual(len(got_files), 2)
  1349. self.assertEqual(got_files[0].LocalPath(), 'eeaee')
  1350. self.assertEqual(got_files[1].LocalPath(), 'eeabee')
  1351. def testLambdaFilter(self):
  1352. files_to_check = presubmit.InputApi.DEFAULT_FILES_TO_SKIP + (
  1353. r".*?a.*?", )
  1354. files_to_skip = [r".*?b.*?"]
  1355. files = [('A', 'eeaee'), ('M', 'eeabee'), ('M', 'eebcee'),
  1356. ('M', 'eecaee')]
  1357. known_files = [
  1358. os.path.join(self.fake_root_dir, item) for _, item in files
  1359. ]
  1360. os.path.isfile.side_effect = lambda f: f in known_files
  1361. change = presubmit.GitChange('mychange',
  1362. '',
  1363. self.fake_root_dir,
  1364. files,
  1365. 0,
  1366. 0,
  1367. None,
  1368. upstream='upstream',
  1369. end_commit='HEAD')
  1370. input_api = presubmit.InputApi(
  1371. change, os.path.join(self.fake_root_dir, 'PRESUBMIT.py'), False,
  1372. None, False)
  1373. # Sample usage of overriding the default white and black lists.
  1374. got_files = input_api.AffectedSourceFiles(
  1375. lambda x: input_api.FilterSourceFile(x, files_to_check,
  1376. files_to_skip))
  1377. self.assertEqual(len(got_files), 2)
  1378. self.assertEqual(got_files[0].LocalPath(), 'eeaee')
  1379. self.assertEqual(got_files[1].LocalPath(), 'eecaee')
  1380. def testGetAbsoluteLocalPath(self):
  1381. normpath = presubmit.normpath
  1382. # Regression test for bug of presubmit stuff that relies on invoking
  1383. # SVN (e.g. to get mime type of file) not working unless gcl invoked
  1384. # from the client root (e.g. if you were at 'src' and did 'cd base'
  1385. # before invoking 'gcl upload' it would fail because svn wouldn't find
  1386. # the files the presubmit script was asking about).
  1387. files = [
  1388. ['A', 'isdir'],
  1389. ['A', os.path.join('isdir', 'blat.cc')],
  1390. ['M', os.path.join('elsewhere', 'ouf.cc')],
  1391. ]
  1392. change = presubmit.Change('mychange', '', self.fake_root_dir, files, 0,
  1393. 0, None)
  1394. affected_files = change.AffectedFiles()
  1395. # Validate that normpath strips trailing path separators.
  1396. self.assertEqual('isdir', normpath('isdir/'))
  1397. # Local paths should remain the same
  1398. self.assertEqual(affected_files[0].LocalPath(), normpath('isdir'))
  1399. self.assertEqual(affected_files[1].LocalPath(),
  1400. normpath('isdir/blat.cc'))
  1401. # Absolute paths should be prefixed
  1402. self.assertEqual(
  1403. affected_files[0].AbsoluteLocalPath(),
  1404. presubmit.normpath(os.path.join(self.fake_root_dir, 'isdir')))
  1405. self.assertEqual(
  1406. affected_files[1].AbsoluteLocalPath(),
  1407. presubmit.normpath(os.path.join(self.fake_root_dir,
  1408. 'isdir/blat.cc')))
  1409. # New helper functions need to work
  1410. paths_from_change = change.AbsoluteLocalPaths()
  1411. self.assertEqual(len(paths_from_change), 3)
  1412. presubmit_path = os.path.join(self.fake_root_dir, 'isdir',
  1413. 'PRESUBMIT.py')
  1414. api = presubmit.InputApi(change=change,
  1415. presubmit_path=presubmit_path,
  1416. is_committing=True,
  1417. gerrit_obj=None,
  1418. verbose=False)
  1419. paths_from_api = api.AbsoluteLocalPaths()
  1420. self.assertEqual(len(paths_from_api), 1)
  1421. self.assertEqual(
  1422. paths_from_change[0],
  1423. presubmit.normpath(os.path.join(self.fake_root_dir, 'isdir')))
  1424. self.assertEqual(
  1425. paths_from_change[1],
  1426. presubmit.normpath(
  1427. os.path.join(self.fake_root_dir, 'isdir', 'blat.cc')))
  1428. self.assertEqual(
  1429. paths_from_api[0],
  1430. presubmit.normpath(
  1431. os.path.join(self.fake_root_dir, 'isdir', 'blat.cc')))
  1432. def testDeprecated(self):
  1433. change = presubmit.Change('mychange', '', self.fake_root_dir, [], 0, 0,
  1434. None)
  1435. api = presubmit.InputApi(
  1436. change, os.path.join(self.fake_root_dir, 'foo', 'PRESUBMIT.py'),
  1437. True, None, False)
  1438. api.AffectedTestableFiles(include_deletes=False)
  1439. def testReadFileStringDenied(self):
  1440. change = presubmit.Change('foo', 'foo', self.fake_root_dir,
  1441. [('M', 'AA')], 0, 0, None)
  1442. input_api = presubmit.InputApi(change,
  1443. os.path.join(self.fake_root_dir, '/p'),
  1444. False, None, False)
  1445. self.assertRaises(IOError, input_api.ReadFile, 'boo', 'x')
  1446. def testReadFileStringAccepted(self):
  1447. path = os.path.join(self.fake_root_dir, 'AA/boo')
  1448. presubmit.gclient_utils.FileRead.return_code = None
  1449. change = presubmit.Change('foo', 'foo', self.fake_root_dir,
  1450. [('M', 'AA')], 0, 0, None)
  1451. input_api = presubmit.InputApi(change,
  1452. os.path.join(self.fake_root_dir, '/p'),
  1453. False, None, False)
  1454. input_api.ReadFile(path, 'x')
  1455. def testReadFileAffectedFileDenied(self):
  1456. fileobj = presubmit.AffectedFile('boo',
  1457. 'M',
  1458. 'Unrelated',
  1459. diff_cache=mock.Mock())
  1460. change = presubmit.Change('foo', 'foo', self.fake_root_dir,
  1461. [('M', 'AA')], 0, 0, None)
  1462. input_api = presubmit.InputApi(change,
  1463. os.path.join(self.fake_root_dir, '/p'),
  1464. False, None, False)
  1465. self.assertRaises(IOError, input_api.ReadFile, fileobj, 'x')
  1466. def testReadFileAffectedFileAccepted(self):
  1467. fileobj = presubmit.AffectedFile('AA/boo',
  1468. 'M',
  1469. self.fake_root_dir,
  1470. diff_cache=mock.Mock())
  1471. presubmit.gclient_utils.FileRead.return_code = None
  1472. change = presubmit.Change('foo', 'foo', self.fake_root_dir,
  1473. [('M', 'AA')], 0, 0, None)
  1474. input_api = presubmit.InputApi(change,
  1475. os.path.join(self.fake_root_dir, '/p'),
  1476. False, None, False)
  1477. input_api.ReadFile(fileobj, 'x')
  1478. def testCreateTemporaryFile(self):
  1479. input_api = presubmit.InputApi(self.fake_change,
  1480. presubmit_path='foo/path/PRESUBMIT.py',
  1481. is_committing=False,
  1482. gerrit_obj=None,
  1483. verbose=False)
  1484. tempfile.NamedTemporaryFile.side_effect = [
  1485. MockTemporaryFile('foo'),
  1486. MockTemporaryFile('bar')
  1487. ]
  1488. self.assertEqual(0, len(input_api._named_temporary_files))
  1489. with input_api.CreateTemporaryFile():
  1490. self.assertEqual(1, len(input_api._named_temporary_files))
  1491. self.assertEqual(['foo'], input_api._named_temporary_files)
  1492. with input_api.CreateTemporaryFile():
  1493. self.assertEqual(2, len(input_api._named_temporary_files))
  1494. self.assertEqual(2, len(input_api._named_temporary_files))
  1495. self.assertEqual(['foo', 'bar'], input_api._named_temporary_files)
  1496. self.assertRaises(TypeError, input_api.CreateTemporaryFile, delete=True)
  1497. self.assertRaises(TypeError,
  1498. input_api.CreateTemporaryFile,
  1499. delete=False)
  1500. self.assertEqual(['foo', 'bar'], input_api._named_temporary_files)
  1501. class OutputApiUnittest(PresubmitTestsBase):
  1502. """Tests presubmit.OutputApi."""
  1503. def testOutputApiBasics(self):
  1504. self.assertIsNotNone(presubmit.OutputApi.PresubmitError('').fatal)
  1505. self.assertFalse(presubmit.OutputApi.PresubmitError('').should_prompt)
  1506. self.assertFalse(presubmit.OutputApi.PresubmitPromptWarning('').fatal)
  1507. self.assertIsNotNone(
  1508. presubmit.OutputApi.PresubmitPromptWarning('').should_prompt)
  1509. self.assertFalse(presubmit.OutputApi.PresubmitNotifyResult('').fatal)
  1510. self.assertFalse(
  1511. presubmit.OutputApi.PresubmitNotifyResult('').should_prompt)
  1512. # TODO(joi) Test MailTextResult once implemented.
  1513. def testAppendCC(self):
  1514. output_api = presubmit.OutputApi(False)
  1515. output_api.AppendCC('chromium-reviews@chromium.org')
  1516. self.assertEqual(['chromium-reviews@chromium.org'], output_api.more_cc)
  1517. def testAppendCCAndMultipleChecks(self):
  1518. description_lines = ('Hello there', 'this is a change', 'BUG=123')
  1519. files = [
  1520. ['A', 'foo\\blat.cc'],
  1521. ]
  1522. fake_presubmit = os.path.join(self.fake_root_dir, 'PRESUBMIT.py')
  1523. change = presubmit.Change('mychange', '\n'.join(description_lines),
  1524. self.fake_root_dir, files, 0, 0, None)
  1525. # Base case: AppendCC from multiple different checks should be reflected
  1526. # in the final result.
  1527. executer = presubmit.PresubmitExecuter(change, True, None,
  1528. presubmit.GerritAccessor())
  1529. self.assertFalse(
  1530. executer.ExecPresubmitScript(
  1531. "PRESUBMIT_VERSION = '2.0.0'\n"
  1532. 'def CheckChangeAddCC1(input_api, output_api):\n'
  1533. " output_api.AppendCC('chromium-reviews@chromium.org')\n"
  1534. ' return []\n'
  1535. '\n'
  1536. 'def CheckChangeAppendCC2(input_api, output_api):\n'
  1537. " output_api.AppendCC('ipc-security-reviews@chromium.org')\n"
  1538. ' return []\n', fake_presubmit))
  1539. self.assertEqual([
  1540. 'chromium-reviews@chromium.org', 'ipc-security-reviews@chromium.org'
  1541. ], executer.more_cc)
  1542. # Check that if one presubmit check appends a CC, it does not get
  1543. # duplicated into the more CC list by subsequent checks.
  1544. executer = presubmit.PresubmitExecuter(change, True, None,
  1545. presubmit.GerritAccessor())
  1546. self.assertFalse(
  1547. executer.ExecPresubmitScript(
  1548. "PRESUBMIT_VERSION = '2.0.0'\n"
  1549. 'def CheckChangeAddCC(input_api, output_api):\n'
  1550. " output_api.AppendCC('chromium-reviews@chromium.org')\n"
  1551. ' return []\n'
  1552. '\n'
  1553. 'def CheckChangeDoNothing(input_api, output_api):\n'
  1554. ' return []\n', fake_presubmit))
  1555. self.assertEqual(['chromium-reviews@chromium.org'], executer.more_cc)
  1556. # Check that if multiple presubmit checks append the same CC, it gets
  1557. # deduplicated.
  1558. executer = presubmit.PresubmitExecuter(change, True, None,
  1559. presubmit.GerritAccessor())
  1560. self.assertFalse(
  1561. executer.ExecPresubmitScript(
  1562. "PRESUBMIT_VERSION = '2.0.0'\n"
  1563. 'def CheckChangeAddCC1(input_api, output_api):\n'
  1564. " output_api.AppendCC('chromium-reviews@chromium.org')\n"
  1565. ' return []\n'
  1566. '\n'
  1567. 'def CheckChangeAppendCC2(input_api, output_api):\n'
  1568. " output_api.AppendCC('chromium-reviews@chromium.org')\n"
  1569. ' return []\n', fake_presubmit))
  1570. self.assertEqual(['chromium-reviews@chromium.org'], executer.more_cc)
  1571. def testOutputApiHandling(self):
  1572. presubmit.OutputApi.PresubmitError('!!!').handle()
  1573. self.assertIsNotNone(sys.stdout.getvalue().count('!!!'))
  1574. sys.stdout.truncate(0)
  1575. presubmit.OutputApi.PresubmitNotifyResult('?see?').handle()
  1576. self.assertIsNotNone(sys.stdout.getvalue().count('?see?'))
  1577. sys.stdout.truncate(0)
  1578. presubmit.OutputApi.PresubmitPromptWarning('???').handle()
  1579. self.assertIsNotNone(sys.stdout.getvalue().count('???'))
  1580. sys.stdout.truncate(0)
  1581. output_api = presubmit.OutputApi(True)
  1582. output_api.PresubmitPromptOrNotify('???').handle()
  1583. self.assertIsNotNone(sys.stdout.getvalue().count('???'))
  1584. sys.stdout.truncate(0)
  1585. output_api = presubmit.OutputApi(False)
  1586. output_api.PresubmitPromptOrNotify('???').handle()
  1587. self.assertIsNotNone(sys.stdout.getvalue().count('???'))
  1588. class AffectedFileUnittest(PresubmitTestsBase):
  1589. def testAffectedFile(self):
  1590. gclient_utils.FileRead.return_value = 'whatever\ncookie'
  1591. af = presubmit.GitAffectedFile('foo/blat.cc', 'M', self.fake_root_dir,
  1592. None)
  1593. self.assertEqual(presubmit.normpath('foo/blat.cc'), af.LocalPath())
  1594. self.assertEqual('M', af.Action())
  1595. self.assertEqual(['whatever', 'cookie'], af.NewContents())
  1596. def testAffectedFileNotExists(self):
  1597. notfound = 'notfound.cc'
  1598. gclient_utils.FileRead.side_effect = IOError
  1599. af = presubmit.AffectedFile(notfound, 'A', self.fake_root_dir, None)
  1600. self.assertEqual([], af.NewContents())
  1601. def testIsTestableFile(self):
  1602. files = [
  1603. presubmit.GitAffectedFile('foo/blat.txt', 'M', self.fake_root_dir,
  1604. None),
  1605. presubmit.GitAffectedFile('foo/binary.blob', 'M',
  1606. self.fake_root_dir, None),
  1607. presubmit.GitAffectedFile('blat/flop.txt', 'D', self.fake_root_dir,
  1608. None)
  1609. ]
  1610. blat = os.path.join('foo', 'blat.txt')
  1611. blob = os.path.join('foo', 'binary.blob')
  1612. f_blat = os.path.join(self.fake_root_dir, blat)
  1613. f_blob = os.path.join(self.fake_root_dir, blob)
  1614. os.path.isfile.side_effect = lambda f: f in [f_blat, f_blob]
  1615. output = list(filter(lambda x: x.IsTestableFile(), files))
  1616. self.assertEqual(2, len(output))
  1617. self.assertEqual(files[:2], output[:2])
  1618. def testGetUnixLocalPath(self):
  1619. # If current platform already uses Unix-style paths,
  1620. # there is nothing to test
  1621. if os.path.sep == '/':
  1622. return
  1623. # If path separator is not forward slash, then we are on Windows and
  1624. # which uses backward slash
  1625. self.assertEqual('\\', os.path.sep)
  1626. cases = [('foo/blat.txt', 'foo/blat.txt'),
  1627. ('foo\\blat.txt', 'foo/blat.txt'),
  1628. ('C:\\development\\src\\chrome\\VERSION',
  1629. 'C:/development/src/chrome/VERSION')]
  1630. for path, expectedUnixLocalPath in cases:
  1631. unixLocalPath = presubmit.GitAffectedFile(path, 'M',
  1632. self.fake_root_dir,
  1633. None).UnixLocalPath()
  1634. self.assertEqual(expectedUnixLocalPath, unixLocalPath)
  1635. def testGetExtension(self):
  1636. cases = [('foo/blat.txt', '.txt'), ('net/features.gni', '.gni'),
  1637. ('archive.tar.gz', '.gz'), ('sub/archive.tar.gz', '.gz'),
  1638. ('.hidden', ''), ('sub/.hidden', ''), ('OWNERS', '')]
  1639. # If current platform uses Windows-style paths, check them too
  1640. if os.path.sep != '/':
  1641. cases.append(('foo\\blat.txt', '.txt'))
  1642. cases.append(('C:\\development\\src\\chrome\\VERSION', ''))
  1643. cases.append(('C:\\development\\src\\.hidden', ''))
  1644. for path, expectedExtension in cases:
  1645. extension = presubmit.GitAffectedFile(path, 'M', self.fake_root_dir,
  1646. None).Extension()
  1647. self.assertEqual(expectedExtension, extension)
  1648. class ChangeUnittest(PresubmitTestsBase):
  1649. def testAffectedFiles(self):
  1650. change = presubmit.Change('', '', self.fake_root_dir, [('Y', 'AA'),
  1651. ('A', 'BB')], 3,
  1652. 5, '')
  1653. self.assertEqual(2, len(change.AffectedFiles()))
  1654. self.assertEqual('Y', change.AffectedFiles()[0].Action())
  1655. @mock.patch('scm.GIT.ListSubmodules', return_value=['BB'])
  1656. def testAffectedSubmodules(self, mockListSubmodules):
  1657. change = presubmit.GitChange('',
  1658. '',
  1659. self.fake_root_dir, [('Y', 'AA'),
  1660. ('A', 'BB')],
  1661. 3,
  1662. 5,
  1663. '',
  1664. upstream='upstream',
  1665. end_commit='HEAD')
  1666. self.assertEqual(1, len(change.AffectedSubmodules()))
  1667. self.assertEqual('A', change.AffectedSubmodules()[0].Action())
  1668. @mock.patch('scm.GIT.ListSubmodules', return_value=['BB'])
  1669. def testAffectedSubmodulesCachesSubmodules(self, mockListSubmodules):
  1670. change = presubmit.GitChange('',
  1671. '',
  1672. self.fake_root_dir, [('Y', 'AA'),
  1673. ('A', 'BB')],
  1674. 3,
  1675. 5,
  1676. '',
  1677. upstream='upstream',
  1678. end_commit='HEAD')
  1679. change.AffectedSubmodules()
  1680. mockListSubmodules.assert_called_once()
  1681. change.AffectedSubmodules()
  1682. mockListSubmodules.assert_called_once()
  1683. def testSetDescriptionText(self):
  1684. change = presubmit.Change('', 'foo\nDRU=ro', self.fake_root_dir, [], 3,
  1685. 5, '')
  1686. self.assertEqual('foo', change.DescriptionText())
  1687. self.assertEqual('foo\nDRU=ro', change.FullDescriptionText())
  1688. self.assertEqual({'DRU': 'ro'}, change.tags)
  1689. change.SetDescriptionText('WHIZ=bang\nbar\nFOO=baz')
  1690. self.assertEqual('bar', change.DescriptionText())
  1691. self.assertEqual('WHIZ=bang\nbar\nFOO=baz',
  1692. change.FullDescriptionText())
  1693. self.assertEqual({'WHIZ': 'bang', 'FOO': 'baz'}, change.tags)
  1694. def testAddDescriptionFooter(self):
  1695. change = presubmit.Change('', 'foo\nDRU=ro\n\nChange-Id: asdf',
  1696. self.fake_root_dir, [], 3, 5, '')
  1697. change.AddDescriptionFooter('my-footer', 'my-value')
  1698. self.assertEqual('foo\nDRU=ro\n\nChange-Id: asdf\nMy-Footer: my-value',
  1699. change.FullDescriptionText())
  1700. def testAddDescriptionFooter_NoPreviousFooters(self):
  1701. change = presubmit.Change('', 'foo\nDRU=ro', self.fake_root_dir, [], 3,
  1702. 5, '')
  1703. change.AddDescriptionFooter('my-footer', 'my-value')
  1704. self.assertEqual('foo\nDRU=ro\n\nMy-Footer: my-value',
  1705. change.FullDescriptionText())
  1706. def testAddDescriptionFooter_InvalidFooter(self):
  1707. change = presubmit.Change('', 'foo\nDRU=ro', self.fake_root_dir, [], 3,
  1708. 5, '')
  1709. with self.assertRaises(ValueError):
  1710. change.AddDescriptionFooter('invalid.characters in:the',
  1711. 'footer key')
  1712. def testGitFootersFromDescription(self):
  1713. change = presubmit.Change(
  1714. '', 'foo\n\nChange-Id: asdf\nBug: 1\nBug: 2\nNo-Try: True',
  1715. self.fake_root_dir, [], 0, 0, '')
  1716. self.assertEqual(
  1717. {
  1718. 'Change-Id': ['asdf'],
  1719. 'Bug': ['2', '1'],
  1720. 'No-Try': ['True'],
  1721. }, change.GitFootersFromDescription())
  1722. def testGitFootersFromDescription_NoFooters(self):
  1723. change = presubmit.Change('', 'foo', self.fake_root_dir, [], 0, 0, '')
  1724. self.assertEqual({}, change.GitFootersFromDescription())
  1725. def testBugFromDescription_FixedAndBugGetDeduped(self):
  1726. change = presubmit.Change(
  1727. '', 'foo\n\nChange-Id: asdf\nBug: 1, 2\nFixed:2, 1 ',
  1728. self.fake_root_dir, [], 0, 0, '')
  1729. self.assertEqual(['1', '2'], change.BugsFromDescription())
  1730. self.assertEqual('1,2', change.BUG)
  1731. def testBugsFromDescription_MixedTagsAndFooters(self):
  1732. change = presubmit.Change('',
  1733. 'foo\nBUG=2,1\n\nChange-Id: asdf\nBug: 3, 6',
  1734. self.fake_root_dir, [], 0, 0, '')
  1735. self.assertEqual(['1', '2', '3', '6'], change.BugsFromDescription())
  1736. self.assertEqual('1,2,3,6', change.BUG)
  1737. def testBugsFromDescription_MultipleFooters(self):
  1738. change = presubmit.Change(
  1739. '', 'foo\n\nChange-Id: asdf\nBug: 1\nBug:4, 6\nFixed: 7',
  1740. self.fake_root_dir, [], 0, 0, '')
  1741. self.assertEqual(['1', '4', '6', '7'], change.BugsFromDescription())
  1742. self.assertEqual('1,4,6,7', change.BUG)
  1743. def testBugFromDescription_OnlyFixed(self):
  1744. change = presubmit.Change('', 'foo\n\nChange-Id: asdf\nFixed:1, 2',
  1745. self.fake_root_dir, [], 0, 0, '')
  1746. self.assertEqual(['1', '2'], change.BugsFromDescription())
  1747. self.assertEqual('1,2', change.BUG)
  1748. def testReviewersFromDescription(self):
  1749. change = presubmit.Change('',
  1750. 'foo\nR=foo,bar\n\nChange-Id: asdf\nR: baz',
  1751. self.fake_root_dir, [], 0, 0, '')
  1752. self.assertEqual(['bar', 'foo'], change.ReviewersFromDescription())
  1753. self.assertEqual('bar,foo', change.R)
  1754. def testTBRsFromDescription(self):
  1755. change = presubmit.Change(
  1756. '', 'foo\nTBR=foo,bar\n\nChange-Id: asdf\nTBR: baz',
  1757. self.fake_root_dir, [], 0, 0, '')
  1758. self.assertEqual(['bar', 'baz', 'foo'], change.TBRsFromDescription())
  1759. self.assertEqual('bar,baz,foo', change.TBR)
  1760. class CannedChecksUnittest(PresubmitTestsBase):
  1761. """Tests presubmit_canned_checks.py."""
  1762. def MockInputApi(self, change, committing, gerrit=None):
  1763. # pylint: disable=no-self-use
  1764. input_api = mock.MagicMock(presubmit.InputApi)
  1765. input_api.thread_pool = presubmit.ThreadPool()
  1766. input_api.parallel = False
  1767. input_api.json = presubmit.json
  1768. input_api.logging = logging
  1769. input_api.os_listdir = mock.Mock()
  1770. input_api.os_walk = mock.Mock()
  1771. input_api.os_path = os.path
  1772. input_api.re = presubmit.re
  1773. input_api.gerrit = gerrit
  1774. input_api.urllib_request = mock.MagicMock(presubmit.urllib_request)
  1775. input_api.urllib_error = mock.MagicMock(presubmit.urllib_error)
  1776. input_api.unittest = unittest
  1777. input_api.subprocess = subprocess
  1778. input_api.sys = sys
  1779. class fake_CalledProcessError(Exception):
  1780. def __str__(self):
  1781. return 'foo'
  1782. input_api.subprocess.CalledProcessError = fake_CalledProcessError
  1783. input_api.verbose = False
  1784. input_api.is_windows = False
  1785. input_api.no_diffs = False
  1786. input_api.change = change
  1787. input_api.is_committing = committing
  1788. input_api.tbr = False
  1789. input_api.dry_run = None
  1790. input_api.python_executable = 'pyyyyython'
  1791. input_api.python3_executable = 'pyyyyython3'
  1792. input_api.platform = sys.platform
  1793. input_api.cpu_count = 2
  1794. input_api.time = time
  1795. input_api.canned_checks = presubmit_canned_checks
  1796. input_api.Command = presubmit.CommandData
  1797. input_api.RunTests = functools.partial(presubmit.InputApi.RunTests,
  1798. input_api)
  1799. return input_api
  1800. def DescriptionTest(self, check, description1, description2, error_type,
  1801. committing):
  1802. change1 = presubmit.Change('foo1', description1, self.fake_root_dir,
  1803. None, 0, 0, None)
  1804. input_api1 = self.MockInputApi(change1, committing)
  1805. change2 = presubmit.Change('foo2', description2, self.fake_root_dir,
  1806. None, 0, 0, None)
  1807. input_api2 = self.MockInputApi(change2, committing)
  1808. results1 = check(input_api1, presubmit.OutputApi)
  1809. self.assertEqual(results1, [])
  1810. results2 = check(input_api2, presubmit.OutputApi)
  1811. self.assertEqual(len(results2), 1)
  1812. self.assertTrue(isinstance(results2[0], error_type))
  1813. def ContentTest(self, check, content1, content1_path, content2,
  1814. content2_path, error_type):
  1815. """Runs a test of a content-checking rule.
  1816. Args:
  1817. check: the check to run.
  1818. content1: content which is expected to pass the check.
  1819. content1_path: file path for content1.
  1820. content2: content which is expected to fail the check.
  1821. content2_path: file path for content2.
  1822. error_type: the type of the error expected for content2.
  1823. """
  1824. change1 = presubmit.Change('foo1', 'foo1\n', self.fake_root_dir, None,
  1825. 0, 0, None)
  1826. input_api1 = self.MockInputApi(change1, False)
  1827. affected_file1 = mock.MagicMock(presubmit.GitAffectedFile)
  1828. input_api1.AffectedFiles.return_value = [affected_file1]
  1829. affected_file1.LocalPath.return_value = content1_path
  1830. affected_file1.NewContents.return_value = [
  1831. 'afoo', content1, 'bfoo', 'cfoo', 'dfoo'
  1832. ]
  1833. # It falls back to ChangedContents when there is a failure. This is an
  1834. # optimization since NewContents() is much faster to execute than
  1835. # ChangedContents().
  1836. affected_file1.ChangedContents.return_value = [(42, content1),
  1837. (43, 'hfoo'),
  1838. (23, 'ifoo')]
  1839. change2 = presubmit.Change('foo2', 'foo2\n', self.fake_root_dir, None,
  1840. 0, 0, None)
  1841. input_api2 = self.MockInputApi(change2, False)
  1842. affected_file2 = mock.MagicMock(presubmit.GitAffectedFile)
  1843. input_api2.AffectedFiles.return_value = [affected_file2]
  1844. affected_file2.LocalPath.return_value = content2_path
  1845. affected_file2.NewContents.return_value = [
  1846. 'dfoo', content2, 'efoo', 'ffoo', 'gfoo'
  1847. ]
  1848. affected_file2.ChangedContents.return_value = [(42, content2),
  1849. (43, 'hfoo'),
  1850. (23, 'ifoo')]
  1851. results1 = check(input_api1, presubmit.OutputApi, None)
  1852. self.assertEqual(results1, [])
  1853. results2 = check(input_api2, presubmit.OutputApi, None)
  1854. self.assertEqual(len(results2), 1)
  1855. self.assertEqual(results2[0].__class__, error_type)
  1856. def PythonLongLineTest(self, maxlen, content, should_pass):
  1857. """Runs a test of Python long-line checking rule.
  1858. Because ContentTest() cannot be used here due to the different code path
  1859. that the implementation of CheckLongLines() uses for Python files.
  1860. Args:
  1861. maxlen: Maximum line length for content.
  1862. content: Python source which is expected to pass or fail the test.
  1863. should_pass: True iff the test should pass, False otherwise.
  1864. """
  1865. change = presubmit.Change('foo1', 'foo1\n', self.fake_root_dir, None, 0,
  1866. 0, None)
  1867. input_api = self.MockInputApi(change, False)
  1868. affected_file = mock.MagicMock(presubmit.GitAffectedFile)
  1869. input_api.AffectedFiles.return_value = [affected_file]
  1870. affected_file.LocalPath.return_value = 'foo.py'
  1871. affected_file.NewContents.return_value = content.splitlines()
  1872. results = presubmit_canned_checks.CheckLongLines(
  1873. input_api, presubmit.OutputApi, maxlen)
  1874. if should_pass:
  1875. self.assertEqual(results, [])
  1876. else:
  1877. self.assertEqual(len(results), 1)
  1878. self.assertEqual(results[0].__class__,
  1879. presubmit.OutputApi.PresubmitPromptWarning)
  1880. def ReadFileTest(self, check, content1, content2, error_type):
  1881. change1 = presubmit.Change('foo1', 'foo1\n', self.fake_root_dir, None,
  1882. 0, 0, None)
  1883. input_api1 = self.MockInputApi(change1, False)
  1884. affected_file1 = mock.MagicMock(presubmit.GitAffectedFile)
  1885. input_api1.AffectedSourceFiles.return_value = [affected_file1]
  1886. input_api1.ReadFile.return_value = content1
  1887. change2 = presubmit.Change('foo2', 'foo2\n', self.fake_root_dir, None,
  1888. 0, 0, None)
  1889. input_api2 = self.MockInputApi(change2, False)
  1890. affected_file2 = mock.MagicMock(presubmit.GitAffectedFile)
  1891. input_api2.AffectedSourceFiles.return_value = [affected_file2]
  1892. input_api2.ReadFile.return_value = content2
  1893. affected_file2.LocalPath.return_value = 'bar.cc'
  1894. results = check(input_api1, presubmit.OutputApi)
  1895. self.assertEqual(results, [])
  1896. results2 = check(input_api2, presubmit.OutputApi)
  1897. self.assertEqual(len(results2), 1)
  1898. self.assertEqual(results2[0].__class__, error_type)
  1899. def testCannedCheckChangeHasBugField(self):
  1900. self.DescriptionTest(presubmit_canned_checks.CheckChangeHasBugField,
  1901. 'Foo\nBUG=b:1234', 'Foo\n',
  1902. presubmit.OutputApi.PresubmitNotifyResult, False)
  1903. def testCannedCheckChangeHasBugFieldWithBuganizerSlash(self):
  1904. self.DescriptionTest(presubmit_canned_checks.CheckChangeHasBugField,
  1905. 'Foo\nBUG=b:1234', 'Foo\nBUG=b/1234',
  1906. presubmit.OutputApi.PresubmitNotifyResult, False)
  1907. def testCannedCheckChangeHasNoUnwantedTags(self):
  1908. self.DescriptionTest(
  1909. presubmit_canned_checks.CheckChangeHasNoUnwantedTags, 'Foo\n',
  1910. 'Foo\nFIXED=1234', presubmit.OutputApi.PresubmitError, False)
  1911. def testCheckChangeHasDescription(self):
  1912. self.DescriptionTest(presubmit_canned_checks.CheckChangeHasDescription,
  1913. 'Bleh', '',
  1914. presubmit.OutputApi.PresubmitNotifyResult, False)
  1915. self.DescriptionTest(presubmit_canned_checks.CheckChangeHasDescription,
  1916. 'Bleh', '', presubmit.OutputApi.PresubmitError,
  1917. True)
  1918. def testCannedCheckDoNotSubmitInDescription(self):
  1919. self.DescriptionTest(
  1920. presubmit_canned_checks.CheckDoNotSubmitInDescription,
  1921. 'Foo\nDO NOTSUBMIT', 'Foo\nDO NOT ' + 'SUBMIT',
  1922. presubmit.OutputApi.PresubmitError, False)
  1923. def testCannedCheckDoNotSubmitInFiles(self):
  1924. self.ContentTest(
  1925. lambda x, y, z: presubmit_canned_checks.CheckDoNotSubmitInFiles(
  1926. x, y), 'DO NOTSUBMIT', None, 'DO NOT ' + 'SUBMIT', None,
  1927. presubmit.OutputApi.PresubmitError)
  1928. def testCannedCheckCorpLinksInDescription(self):
  1929. self.DescriptionTest(
  1930. presubmit_canned_checks.CheckCorpLinksInDescription,
  1931. 'chromium.googlesource.com', 'chromium.git.corp.google.com',
  1932. presubmit.OutputApi.PresubmitPromptWarning, False)
  1933. def testCannedCheckCorpLinksInFiles(self):
  1934. self.ContentTest(presubmit_canned_checks.CheckCorpLinksInFiles,
  1935. 'chromium.googlesource.com', None,
  1936. 'chromium.git.corp.google.com', None,
  1937. presubmit.OutputApi.PresubmitPromptWarning)
  1938. def testCannedCheckLargeScaleChange(self):
  1939. input_api = self.MockInputApi(
  1940. presubmit.Change('foo', 'foo1', self.fake_root_dir, None, 0, 0,
  1941. None), False)
  1942. affected_files = []
  1943. for i in range(100):
  1944. affected_file = mock.MagicMock(presubmit.GitAffectedFile)
  1945. affected_file.LocalPath.return_value = f'foo{i}.cc'
  1946. affected_files.append(affected_file)
  1947. input_api.AffectedFiles = lambda **_: affected_files
  1948. # Don't warn if less than or equal to 100 files.
  1949. results = presubmit_canned_checks.CheckLargeScaleChange(
  1950. input_api, presubmit.OutputApi)
  1951. self.assertEqual(len(results), 0)
  1952. # Warn if greater than 100 files.
  1953. affected_files.append('bar.cc')
  1954. results = presubmit_canned_checks.CheckLargeScaleChange(
  1955. input_api, presubmit.OutputApi)
  1956. self.assertEqual(len(results), 1)
  1957. result = results[0]
  1958. self.assertEqual(result.__class__,
  1959. presubmit.OutputApi.PresubmitPromptWarning)
  1960. self.assertIn("large scale change", result.json_format()['message'])
  1961. def testCheckChangeHasNoStrayWhitespace(self):
  1962. self.ContentTest(
  1963. lambda x, y, z: presubmit_canned_checks.
  1964. CheckChangeHasNoStrayWhitespace(x, y), 'Foo', None, 'Foo ', None,
  1965. presubmit.OutputApi.PresubmitPromptWarning)
  1966. def testCheckChangeHasOnlyOneEol(self):
  1967. self.ReadFileTest(presubmit_canned_checks.CheckChangeHasOnlyOneEol,
  1968. "Hey!\nHo!\n", "Hey!\nHo!\n\n",
  1969. presubmit.OutputApi.PresubmitPromptWarning)
  1970. def testCheckChangeHasNoCR(self):
  1971. self.ReadFileTest(presubmit_canned_checks.CheckChangeHasNoCR,
  1972. "Hey!\nHo!\n", "Hey!\r\nHo!\r\n",
  1973. presubmit.OutputApi.PresubmitPromptWarning)
  1974. def testCheckChangeHasNoCrAndHasOnlyOneEol(self):
  1975. self.ReadFileTest(
  1976. presubmit_canned_checks.CheckChangeHasNoCrAndHasOnlyOneEol,
  1977. "Hey!\nHo!\n", "Hey!\nHo!\n\n",
  1978. presubmit.OutputApi.PresubmitPromptWarning)
  1979. self.ReadFileTest(
  1980. presubmit_canned_checks.CheckChangeHasNoCrAndHasOnlyOneEol,
  1981. "Hey!\nHo!\n", "Hey!\r\nHo!\r\n",
  1982. presubmit.OutputApi.PresubmitPromptWarning)
  1983. def testCheckChangeTodoHasOwner(self):
  1984. self.ContentTest(presubmit_canned_checks.CheckChangeTodoHasOwner,
  1985. "TODO: foo - bar", None, "TODO: bar", None,
  1986. presubmit.OutputApi.PresubmitPromptWarning)
  1987. self.ContentTest(presubmit_canned_checks.CheckChangeTodoHasOwner,
  1988. "TODO(foo): bar", None, "TODO: bar", None,
  1989. presubmit.OutputApi.PresubmitPromptWarning)
  1990. @mock.patch('git_cl.Changelist')
  1991. @mock.patch('auth.Authenticator')
  1992. def testCannedCheckChangedLUCIConfigsRoot(self, mockGetAuth, mockCl):
  1993. affected_file1 = mock.MagicMock(presubmit.GitAffectedFile)
  1994. affected_file1.LocalPath.return_value = 'foo.cfg'
  1995. affected_file2 = mock.MagicMock(presubmit.GitAffectedFile)
  1996. affected_file2.LocalPath.return_value = 'sub/bar.cfg'
  1997. mockGetAuth().get_id_token().token = 123
  1998. host = 'https://host.com'
  1999. branch = 'branch'
  2000. http_resp = b")]}'\n" + json.dumps({
  2001. 'configSets': [{
  2002. 'name': 'project/deadbeef',
  2003. 'url': '%s/+/%s' % (host, branch)
  2004. }]
  2005. }).encode("utf-8")
  2006. mockCl().GetRemoteBranch.return_value = ('remote', branch)
  2007. mockCl().GetRemoteUrl.return_value = host
  2008. change1 = presubmit.Change('foo', 'foo1', self.fake_root_dir, None, 0,
  2009. 0, None)
  2010. input_api = self.MockInputApi(change1, False)
  2011. input_api.urllib_request.urlopen().read.return_value = http_resp
  2012. affected_files = (affected_file1, affected_file2)
  2013. input_api.AffectedFiles = lambda **_: affected_files
  2014. proc = mock.Mock()
  2015. proc.communicate.return_value = ('This is STDOUT', 'This is STDERR')
  2016. proc.returncode = 0
  2017. subprocess.Popen.return_value = proc
  2018. input_api.CreateTemporaryFile.return_value = MockTemporaryFile(
  2019. 'tmp_file')
  2020. validation_result = {
  2021. 'result': {
  2022. 'validation': [{
  2023. 'messages': [{
  2024. 'path': 'foo.cfg',
  2025. 'severity': 'ERROR',
  2026. 'text': 'deadbeef',
  2027. }, {
  2028. 'path': 'sub/bar.cfg',
  2029. 'severity': 'WARNING',
  2030. 'text': 'cafecafe',
  2031. }]
  2032. }]
  2033. }
  2034. }
  2035. json.load.return_value = validation_result
  2036. results = presubmit_canned_checks.CheckChangedLUCIConfigs(
  2037. input_api, presubmit.OutputApi)
  2038. self.assertEqual(len(results), 2)
  2039. self.assertEqual(results[0].json_format()['message'],
  2040. "Config validation for file(foo.cfg): deadbeef")
  2041. self.assertEqual(results[1].json_format()['message'],
  2042. "Config validation for file(sub/bar.cfg): cafecafe")
  2043. subprocess.Popen.assert_called_once_with([
  2044. 'lucicfg' + ('.bat' if input_api.is_windows else ''), 'validate',
  2045. '.', '-config-set', 'project/deadbeef', '-log-level',
  2046. 'debug' if input_api.verbose else 'warning', '-json-output',
  2047. 'tmp_file'
  2048. ],
  2049. cwd=self.fake_root_dir,
  2050. stderr=subprocess.PIPE,
  2051. shell=input_api.is_windows)
  2052. @mock.patch('git_cl.Changelist')
  2053. @mock.patch('auth.Authenticator')
  2054. def testCannedCheckChangedLUCIConfigsNoFile(self, mockGetAuth, mockCl):
  2055. affected_file1 = mock.MagicMock(presubmit.GitAffectedFile)
  2056. affected_file1.LocalPath.return_value = 'foo.cfg'
  2057. affected_file2 = mock.MagicMock(presubmit.GitAffectedFile)
  2058. affected_file2.LocalPath.return_value = 'bar.cfg'
  2059. mockGetAuth().get_id_token().token = 123
  2060. host = 'https://host.com'
  2061. branch = 'branch'
  2062. http_resp = b")]}'\n" + json.dumps({
  2063. 'configSets': [{
  2064. 'name': 'project/deadbeef',
  2065. 'url': '%s/+/%s/generated' % (host, branch)
  2066. # no affected file in generated folder
  2067. }]
  2068. }).encode("utf-8")
  2069. mockCl().GetRemoteBranch.return_value = ('remote', branch)
  2070. mockCl().GetRemoteUrl.return_value = host
  2071. change1 = presubmit.Change('foo', 'foo1', self.fake_root_dir, None, 0,
  2072. 0, None)
  2073. input_api = self.MockInputApi(change1, False)
  2074. input_api.urllib_request.urlopen().read.return_value = http_resp
  2075. affected_files = (affected_file1, affected_file2)
  2076. input_api.AffectedFiles = lambda **_: affected_files
  2077. results = presubmit_canned_checks.CheckChangedLUCIConfigs(
  2078. input_api, presubmit.OutputApi)
  2079. self.assertEqual(len(results), 0)
  2080. @mock.patch('git_cl.Changelist')
  2081. @mock.patch('auth.Authenticator')
  2082. def testCannedCheckChangedLUCIConfigsNonRoot(self, mockGetAuth, mockCl):
  2083. affected_file1 = mock.MagicMock(presubmit.GitAffectedFile)
  2084. affected_file1.LocalPath.return_value = 'generated/foo.cfg'
  2085. affected_file2 = mock.MagicMock(presubmit.GitAffectedFile)
  2086. affected_file2.LocalPath.return_value = 'generated/bar.cfg'
  2087. mockGetAuth().get_access_token().token = 123
  2088. host = 'https://host.com'
  2089. branch = 'branch'
  2090. http_resp = b")]}'\n" + json.dumps({
  2091. 'configSets': [{
  2092. 'name': 'project/deadbeef',
  2093. 'url': '%s/+/%s/generated' % (host, branch)
  2094. }]
  2095. }).encode("utf-8")
  2096. mockCl().GetRemoteBranch.return_value = ('remote', branch)
  2097. mockCl().GetRemoteUrl.return_value = host
  2098. change1 = presubmit.Change('foo', 'foo1', self.fake_root_dir, None, 0,
  2099. 0, None)
  2100. input_api = self.MockInputApi(change1, False)
  2101. input_api.urllib_request.urlopen().read.return_value = http_resp
  2102. affected_files = (affected_file1, affected_file2)
  2103. input_api.AffectedFiles = lambda **_: affected_files
  2104. proc = mock.Mock()
  2105. proc.communicate.return_value = ('This is STDOUT', 'This is STDERR')
  2106. proc.returncode = 0
  2107. subprocess.Popen.return_value = proc
  2108. input_api.CreateTemporaryFile.return_value = MockTemporaryFile(
  2109. 'tmp_file')
  2110. validation_result = {
  2111. 'result': {
  2112. 'validation': [{
  2113. 'messages': [
  2114. {
  2115. 'path': 'bar.cfg',
  2116. 'severity': 'ERROR',
  2117. 'text': 'deadbeef',
  2118. },
  2119. {
  2120. 'path': 'sub/baz.cfg', # not an affected file
  2121. 'severity': 'ERROR',
  2122. 'text': 'cafecafe',
  2123. }
  2124. ]
  2125. }]
  2126. }
  2127. }
  2128. json.load.return_value = validation_result
  2129. results = presubmit_canned_checks.CheckChangedLUCIConfigs(
  2130. input_api, presubmit.OutputApi)
  2131. self.assertEqual(len(results), 2)
  2132. self.assertEqual(results[0].json_format()['message'],
  2133. "Config validation for file(bar.cfg): deadbeef")
  2134. self.assertEqual(
  2135. results[1].json_format()['message'],
  2136. "Found 1 additional errors/warnings in files that are not modified,"
  2137. " run `lucicfg validate %s%sgenerated -config-set "
  2138. "project/deadbeef` to reveal them" %
  2139. (self.fake_root_dir, self._OS_SEP))
  2140. subprocess.Popen.assert_called_once_with([
  2141. 'lucicfg' + ('.bat' if input_api.is_windows else ''),
  2142. 'validate',
  2143. 'generated',
  2144. '-config-set',
  2145. 'project/deadbeef',
  2146. '-log-level',
  2147. 'debug' if input_api.verbose else 'warning',
  2148. '-json-output',
  2149. 'tmp_file',
  2150. ],
  2151. cwd=self.fake_root_dir,
  2152. stderr=subprocess.PIPE,
  2153. shell=input_api.is_windows)
  2154. @mock.patch('git_cl.Changelist')
  2155. @mock.patch('auth.Authenticator')
  2156. def testCannedCheckChangedLUCIConfigsGerritInfo(self, mockGetAuth, mockCl):
  2157. affected_file = mock.MagicMock(presubmit.ProvidedDiffAffectedFile)
  2158. affected_file.LocalPath.return_value = 'foo.cfg'
  2159. mockGetAuth().get_id_token().token = 123
  2160. host = 'host.googlesource.com'
  2161. project = 'project/deadbeef'
  2162. branch = 'branch'
  2163. http_resp = b")]}'\n" + json.dumps({
  2164. 'configSets':
  2165. [{
  2166. 'name': 'project/deadbeef',
  2167. 'url': f'https://{host}/{project}/+/{branch}/generated'
  2168. # no affected file in generated folder
  2169. }]
  2170. }).encode("utf-8")
  2171. gerrit_mock = mock.MagicMock(presubmit.GerritAccessor)
  2172. gerrit_mock.host = host
  2173. gerrit_mock.project = project
  2174. gerrit_mock.branch = branch
  2175. change1 = presubmit.Change('foo', 'foo1', self.fake_root_dir, None, 0,
  2176. 0, None)
  2177. input_api = self.MockInputApi(change1, False, gerrit_mock)
  2178. input_api.urllib_request.urlopen().read.return_value = http_resp
  2179. input_api.AffectedFiles = lambda **_: (affected_file, )
  2180. results = presubmit_canned_checks.CheckChangedLUCIConfigs(
  2181. input_api, presubmit.OutputApi)
  2182. self.assertEqual(len(results), 0)
  2183. def testCannedCheckChangeHasNoTabs(self):
  2184. self.ContentTest(presubmit_canned_checks.CheckChangeHasNoTabs,
  2185. 'blah blah', None, 'blah\tblah', None,
  2186. presubmit.OutputApi.PresubmitPromptWarning)
  2187. # Make sure makefiles are ignored.
  2188. change1 = presubmit.Change('foo1', 'foo1\n', self.fake_root_dir, None,
  2189. 0, 0, None)
  2190. input_api1 = self.MockInputApi(change1, False)
  2191. affected_file1 = mock.MagicMock(presubmit.GitAffectedFile)
  2192. affected_file1.LocalPath.return_value = 'foo.cc'
  2193. affected_file2 = mock.MagicMock(presubmit.GitAffectedFile)
  2194. affected_file2.LocalPath.return_value = 'foo/Makefile'
  2195. affected_file3 = mock.MagicMock(presubmit.GitAffectedFile)
  2196. affected_file3.LocalPath.return_value = 'makefile'
  2197. # Only this one will trigger.
  2198. affected_file4 = mock.MagicMock(presubmit.GitAffectedFile)
  2199. affected_file1.LocalPath.return_value = 'foo.cc'
  2200. affected_file1.NewContents.return_value = ['yo, ']
  2201. affected_file4.LocalPath.return_value = 'makefile.foo'
  2202. affected_file4.LocalPath.return_value = 'makefile.foo'
  2203. affected_file4.NewContents.return_value = ['ye\t']
  2204. affected_file4.ChangedContents.return_value = [(46, 'ye\t')]
  2205. affected_file4.LocalPath.return_value = 'makefile.foo'
  2206. affected_files = (affected_file1, affected_file2, affected_file3,
  2207. affected_file4)
  2208. def test(include_deletes=True, file_filter=None):
  2209. self.assertFalse(include_deletes)
  2210. for x in affected_files:
  2211. if file_filter(x):
  2212. yield x
  2213. # Override the mock of these functions.
  2214. input_api1.FilterSourceFile = lambda x: x
  2215. input_api1.AffectedFiles = test
  2216. results1 = presubmit_canned_checks.CheckChangeHasNoTabs(
  2217. input_api1, presubmit.OutputApi, None)
  2218. self.assertEqual(len(results1), 1)
  2219. self.assertEqual(results1[0].__class__,
  2220. presubmit.OutputApi.PresubmitPromptWarning)
  2221. self.assertEqual(results1[0]._long_text, 'makefile.foo:46')
  2222. def testCannedCheckLongLines(self):
  2223. check = lambda x, y, z: presubmit_canned_checks.CheckLongLines(
  2224. x, y, 10, z)
  2225. self.ContentTest(check, '0123456789', None, '01234567890', None,
  2226. presubmit.OutputApi.PresubmitPromptWarning)
  2227. def testCannedCheckJavaLongLines(self):
  2228. check = lambda x, y, _: presubmit_canned_checks.CheckLongLines(x, y, 80)
  2229. self.ContentTest(check, 'A ' * 50, 'foo.java', 'A ' * 50 + 'B',
  2230. 'foo.java', presubmit.OutputApi.PresubmitPromptWarning)
  2231. def testCannedCheckSpecialJavaLongLines(self):
  2232. check = lambda x, y, _: presubmit_canned_checks.CheckLongLines(x, y, 80)
  2233. self.ContentTest(check, 'import ' + 'A ' * 150, 'foo.java',
  2234. 'importSomething ' + 'A ' * 50, 'foo.java',
  2235. presubmit.OutputApi.PresubmitPromptWarning)
  2236. def testCannedCheckPythonLongLines(self):
  2237. # NOTE: Cannot use ContentTest() here because of the different code path
  2238. # used for Python checks in CheckLongLines().
  2239. passing_cases = [
  2240. r"""
  2241. 01234568901234589012345689012345689
  2242. A short line
  2243. """,
  2244. r"""
  2245. 01234568901234589012345689012345689
  2246. This line is too long but should pass # pylint: disable=line-too-long
  2247. """,
  2248. r"""
  2249. 01234568901234589012345689012345689
  2250. # pylint: disable=line-too-long
  2251. This line is too long but should pass due to global disable
  2252. """,
  2253. r"""
  2254. 01234568901234589012345689012345689
  2255. #pylint: disable=line-too-long
  2256. This line is too long but should pass due to global disable.
  2257. """,
  2258. r"""
  2259. 01234568901234589012345689012345689
  2260. # pylint: disable=line-too-long
  2261. This line is too long but should pass due to global disable.
  2262. """,
  2263. r"""
  2264. 01234568901234589012345689012345689
  2265. # import is a valid exception
  2266. import some.really.long.package.name.that.should.pass
  2267. """,
  2268. r"""
  2269. 01234568901234589012345689012345689
  2270. # from is a valid exception
  2271. from some.really.long.package.name import passing.line
  2272. """,
  2273. r"""
  2274. 01234568901234589012345689012345689
  2275. import some.package
  2276. """,
  2277. r"""
  2278. 01234568901234589012345689012345689
  2279. from some.package import stuff
  2280. """,
  2281. ]
  2282. for content in passing_cases:
  2283. self.PythonLongLineTest(40, content, should_pass=True)
  2284. failing_cases = [
  2285. r"""
  2286. 01234568901234589012345689012345689
  2287. This line is definitely too long and should fail.
  2288. """,
  2289. r"""
  2290. 01234568901234589012345689012345689
  2291. # pylint: disable=line-too-long
  2292. This line is too long and should pass due to global disable
  2293. # pylint: enable=line-too-long
  2294. But this line is too long and should still fail now
  2295. """,
  2296. r"""
  2297. 01234568901234589012345689012345689
  2298. # pylint: disable=line-too-long
  2299. This line is too long and should pass due to global disable
  2300. But this line is too long # pylint: enable=line-too-long
  2301. """,
  2302. r"""
  2303. 01234568901234589012345689012345689
  2304. This should fail because the global
  2305. check is enabled on the next line.
  2306. # pylint: enable=line-too-long
  2307. """,
  2308. r"""
  2309. 01234568901234589012345689012345689
  2310. # pylint: disable=line-too-long
  2311. # pylint: enable-foo-bar should pass
  2312. The following line should fail
  2313. since global directives apply to
  2314. the current line as well!
  2315. # pylint: enable-line-too-long should fail
  2316. """,
  2317. ]
  2318. for content in failing_cases[0:0]:
  2319. self.PythonLongLineTest(40, content, should_pass=False)
  2320. def testCannedCheckJSLongLines(self):
  2321. check = lambda x, y, _: presubmit_canned_checks.CheckLongLines(x, y, 10)
  2322. self.ContentTest(check, 'GEN(\'#include "c/b/ui/webui/fixture.h"\');',
  2323. 'foo.js', "// GEN('something');", 'foo.js',
  2324. presubmit.OutputApi.PresubmitPromptWarning)
  2325. def testCannedCheckJSLongImports(self):
  2326. check = lambda x, y, _: presubmit_canned_checks.CheckLongLines(x, y, 10)
  2327. self.ContentTest(check,
  2328. "import {Name, otherName} from './dir/file.js';",
  2329. 'foo.js', "// We should import something long, eh?",
  2330. 'foo.js', presubmit.OutputApi.PresubmitPromptWarning)
  2331. def testCannedCheckTSLongImports(self):
  2332. check = lambda x, y, _: presubmit_canned_checks.CheckLongLines(x, y, 10)
  2333. self.ContentTest(check, "import {Name, otherName} from './dir/file';",
  2334. 'foo.ts', "// We should import something long, eh?",
  2335. 'foo.ts', presubmit.OutputApi.PresubmitPromptWarning)
  2336. def testCannedCheckObjCExceptionLongLines(self):
  2337. check = lambda x, y, _: presubmit_canned_checks.CheckLongLines(x, y, 80)
  2338. self.ContentTest(check, '#import ' + 'A ' * 150, 'foo.mm',
  2339. 'import' + 'A ' * 150, 'foo.mm',
  2340. presubmit.OutputApi.PresubmitPromptWarning)
  2341. def testCannedCheckMakefileLongLines(self):
  2342. check = lambda x, y, _: presubmit_canned_checks.CheckLongLines(x, y, 80)
  2343. self.ContentTest(check, 'A ' * 100, 'foo.mk', 'A ' * 100 + 'B',
  2344. 'foo.mk', presubmit.OutputApi.PresubmitPromptWarning)
  2345. def testCannedCheckLongLinesLF(self):
  2346. check = lambda x, y, z: presubmit_canned_checks.CheckLongLines(
  2347. x, y, 10, z)
  2348. self.ContentTest(check, '012345678\n', None, '0123456789\n', None,
  2349. presubmit.OutputApi.PresubmitPromptWarning)
  2350. def testCannedCheckCppExceptionLongLines(self):
  2351. check = lambda x, y, z: presubmit_canned_checks.CheckLongLines(
  2352. x, y, 10, z)
  2353. self.ContentTest(check, '#if 56 89 12 45 9191919191919', 'foo.cc',
  2354. '#nif 56 89 12 45 9191919191919', 'foo.cc',
  2355. presubmit.OutputApi.PresubmitPromptWarning)
  2356. def testCannedCheckLongLinesHttp(self):
  2357. check = lambda x, y, z: presubmit_canned_checks.CheckLongLines(
  2358. x, y, 10, z)
  2359. self.ContentTest(check, ' http:// 0 23 56', None, ' foob:// 0 23 56',
  2360. None, presubmit.OutputApi.PresubmitPromptWarning)
  2361. def testCannedCheckLongLinesFile(self):
  2362. check = lambda x, y, z: presubmit_canned_checks.CheckLongLines(
  2363. x, y, 10, z)
  2364. self.ContentTest(check, ' file:// 0 23 56', None, ' foob:// 0 23 56',
  2365. None, presubmit.OutputApi.PresubmitPromptWarning)
  2366. def testCannedCheckLongLinesCssUrl(self):
  2367. check = lambda x, y, z: presubmit_canned_checks.CheckLongLines(
  2368. x, y, 10, z)
  2369. self.ContentTest(check, ' url(some.png)', 'foo.css', ' url(some.png)',
  2370. 'foo.cc', presubmit.OutputApi.PresubmitPromptWarning)
  2371. def testCannedCheckLongLinesLongSymbol(self):
  2372. check = lambda x, y, z: presubmit_canned_checks.CheckLongLines(
  2373. x, y, 10, z)
  2374. self.ContentTest(check, ' TUP5D_LoNG_SY ', None, ' TUP5D_LoNG_SY5 ',
  2375. None, presubmit.OutputApi.PresubmitPromptWarning)
  2376. def _LicenseCheck(self,
  2377. text,
  2378. license_text,
  2379. committing,
  2380. expected_result,
  2381. new_file=False,
  2382. **kwargs):
  2383. change = mock.MagicMock(presubmit.GitChange)
  2384. change.scm = 'svn'
  2385. input_api = self.MockInputApi(change, committing)
  2386. affected_file = mock.MagicMock(presubmit.GitAffectedFile)
  2387. if new_file:
  2388. affected_file.Action.return_value = 'A'
  2389. input_api.AffectedSourceFiles.return_value = [affected_file]
  2390. input_api.ReadFile.return_value = text
  2391. if expected_result:
  2392. affected_file.LocalPath.return_value = 'bleh'
  2393. result = presubmit_canned_checks.CheckLicense(input_api,
  2394. presubmit.OutputApi,
  2395. license_text,
  2396. source_file_filter=42,
  2397. **kwargs)
  2398. if expected_result:
  2399. self.assertEqual(len(result), 1)
  2400. self.assertEqual(result[0].__class__, expected_result)
  2401. else:
  2402. self.assertEqual(result, [])
  2403. def testCheckLicenseSuccess(self):
  2404. text = ("#!/bin/python\n"
  2405. "# Copyright (c) 2037 Nobody.\n"
  2406. "# All Rights Reserved.\n"
  2407. "print('foo')\n")
  2408. license_text = (r".*? Copyright \(c\) 2037 Nobody.\n"
  2409. r".*? All Rights Reserved\.\n")
  2410. self._LicenseCheck(text, license_text, True, None)
  2411. def testCheckLicenseSuccessNew(self):
  2412. # Make sure the license check works on new files with custom licenses.
  2413. text = ("#!/bin/python\n"
  2414. "# Copyright (c) 2037 Nobody.\n"
  2415. "# All Rights Reserved.\n"
  2416. "print('foo')\n")
  2417. license_text = (r".*? Copyright \(c\) 2037 Nobody.\n"
  2418. r".*? All Rights Reserved\.\n")
  2419. self._LicenseCheck(text, license_text, True, None, new_file=True)
  2420. def testCheckLicenseFailCommit(self):
  2421. text = ("#!/bin/python\n"
  2422. "# Copyright (c) 2037 Nobody.\n"
  2423. "# All Rights Reserved.\n"
  2424. "print('foo')\n")
  2425. license_text = (r".*? Copyright \(c\) 0007 Nobody.\n"
  2426. r".*? All Rights Reserved\.\n")
  2427. self._LicenseCheck(text, license_text, True,
  2428. presubmit.OutputApi.PresubmitPromptWarning)
  2429. def testCheckLicenseFailUpload(self):
  2430. text = ("#!/bin/python\n"
  2431. "# Copyright (c) 2037 Nobody.\n"
  2432. "# All Rights Reserved.\n"
  2433. "print('foo')\n")
  2434. license_text = (r".*? Copyright \(c\) 0007 Nobody.\n"
  2435. r".*? All Rights Reserved\.\n")
  2436. self._LicenseCheck(text, license_text, False,
  2437. presubmit.OutputApi.PresubmitPromptWarning)
  2438. def testCheckLicenseEmptySuccess(self):
  2439. text = ''
  2440. license_text = (r".*? Copyright \(c\) 2037 Nobody.\n"
  2441. r".*? All Rights Reserved\.\n")
  2442. self._LicenseCheck(text,
  2443. license_text,
  2444. True,
  2445. None,
  2446. accept_empty_files=True)
  2447. def testCheckLicenseNewFilePass(self):
  2448. text = self._GetLicenseText(int(time.strftime('%Y')))
  2449. license_text = None
  2450. self._LicenseCheck(text, license_text, False, None, new_file=True)
  2451. def testCheckLicenseNewFileFail(self):
  2452. # Check that we fail on new files with the (c) symbol.
  2453. current_year = int(time.strftime('%Y'))
  2454. text = (
  2455. "#!/bin/python\n"
  2456. "# Copyright (c) %d The Chromium Authors\n"
  2457. "# Use of this source code is governed by a BSD-style license that can "
  2458. "be\n"
  2459. "# found in the LICENSE file.\n"
  2460. "print('foo')\n" % current_year)
  2461. license_text = None
  2462. self._LicenseCheck(text,
  2463. license_text,
  2464. False,
  2465. presubmit.OutputApi.PresubmitPromptWarning,
  2466. new_file=True)
  2467. def _GetLicenseText(self, current_year):
  2468. return (
  2469. "#!/bin/python\n"
  2470. "# Copyright %d The Chromium Authors\n"
  2471. "# Use of this source code is governed by a BSD-style license that can "
  2472. "be\n"
  2473. "# found in the LICENSE file.\n"
  2474. "print('foo')\n" % current_year)
  2475. def testCheckLicenseNewFileWarn(self):
  2476. # Check that we warn on new files with wrong year. Test with first
  2477. # allowed year.
  2478. text = self._GetLicenseText(2006)
  2479. license_text = None
  2480. self._LicenseCheck(text,
  2481. license_text,
  2482. False,
  2483. presubmit.OutputApi.PresubmitPromptWarning,
  2484. new_file=True)
  2485. def testCheckLicenseNewCSSFilePass(self):
  2486. # Check that CSS-style comments in license text are supported.
  2487. current_year = int(time.strftime('%Y'))
  2488. text = (
  2489. "/* Copyright %d The Chromium Authors\n"
  2490. " * Use of this source code is governed by a BSD-style license that "
  2491. "can be\n"
  2492. "* found in the LICENSE file. */\n"
  2493. "\n"
  2494. "h1 {}\n" % current_year)
  2495. license_text = None
  2496. self._LicenseCheck(text, license_text, False, None, new_file=True)
  2497. def testCheckLicenseNewXMLFilePass(self):
  2498. # Check that XML-style comments in license text are supported.
  2499. current_year = int(time.strftime('%Y'))
  2500. text = (
  2501. '<?xml version="1.0" encoding="utf-8"?>\n'
  2502. '<!--\n'
  2503. 'Copyright %d The Chromium Authors\n'
  2504. 'Use of this source code is governed by a BSD-style license that '
  2505. 'can be\n'
  2506. 'found in the LICENSE file.\n'
  2507. '-->\n'
  2508. '<root/>\n' % current_year)
  2509. license_text = None
  2510. self._LicenseCheck(text, license_text, False, None, new_file=True)
  2511. def testCannedCheckTreeIsOpenOpen(self):
  2512. input_api = self.MockInputApi(None, True)
  2513. input_api.urllib_request.urlopen(
  2514. ).read.return_value = 'The tree is open'
  2515. results = presubmit_canned_checks.CheckTreeIsOpen(input_api,
  2516. presubmit.OutputApi,
  2517. url='url_to_open',
  2518. closed='.*closed.*')
  2519. self.assertEqual(results, [])
  2520. def testCannedCheckTreeIsOpenClosed(self):
  2521. input_api = self.MockInputApi(None, True)
  2522. input_api.urllib_request.urlopen().read.return_value = (
  2523. 'Tree is closed for maintenance')
  2524. results = presubmit_canned_checks.CheckTreeIsOpen(input_api,
  2525. presubmit.OutputApi,
  2526. url='url_to_closed',
  2527. closed='.*closed.*')
  2528. self.assertEqual(len(results), 1)
  2529. self.assertEqual(results[0].__class__,
  2530. presubmit.OutputApi.PresubmitError)
  2531. def testCannedCheckJsonTreeIsOpenOpen(self):
  2532. input_api = self.MockInputApi(None, True)
  2533. status = {
  2534. 'can_commit_freely': True,
  2535. 'general_state': 'open',
  2536. 'message': 'The tree is open'
  2537. }
  2538. input_api.urllib_request.urlopen().read.return_value = json.dumps(
  2539. status)
  2540. results = presubmit_canned_checks.CheckTreeIsOpen(
  2541. input_api, presubmit.OutputApi, json_url='url_to_open')
  2542. self.assertEqual(results, [])
  2543. def testCannedCheckJsonTreeIsOpenClosed(self):
  2544. input_api = self.MockInputApi(None, True)
  2545. status = {
  2546. 'can_commit_freely': False,
  2547. 'general_state': 'closed',
  2548. 'message': 'The tree is close',
  2549. }
  2550. input_api.urllib_request.urlopen().read.return_value = json.dumps(
  2551. status)
  2552. results = presubmit_canned_checks.CheckTreeIsOpen(
  2553. input_api, presubmit.OutputApi, json_url='url_to_closed')
  2554. self.assertEqual(len(results), 1)
  2555. self.assertEqual(results[0].__class__,
  2556. presubmit.OutputApi.PresubmitError)
  2557. def testRunPythonUnitTestsNoTest(self):
  2558. input_api = self.MockInputApi(None, False)
  2559. presubmit_canned_checks.RunPythonUnitTests(input_api,
  2560. presubmit.OutputApi, [])
  2561. results = input_api.thread_pool.RunAsync()
  2562. self.assertEqual(results, [])
  2563. def testRunPythonUnitTestsNonExistentUpload(self):
  2564. input_api = self.MockInputApi(None, False)
  2565. subprocess.Popen().returncode = 1 # pylint: disable=no-value-for-parameter
  2566. presubmit.sigint_handler.wait.return_value = (b'foo', None)
  2567. results = presubmit_canned_checks.RunPythonUnitTests(
  2568. input_api, presubmit.OutputApi, ['_non_existent_module'])
  2569. self.assertEqual(len(results), 1)
  2570. self.assertEqual(results[0].__class__,
  2571. presubmit.OutputApi.PresubmitNotifyResult)
  2572. def testRunPythonUnitTestsNonExistentCommitting(self):
  2573. input_api = self.MockInputApi(None, True)
  2574. subprocess.Popen().returncode = 1 # pylint: disable=no-value-for-parameter
  2575. presubmit.sigint_handler.wait.return_value = (b'foo', None)
  2576. results = presubmit_canned_checks.RunPythonUnitTests(
  2577. input_api, presubmit.OutputApi, ['_non_existent_module'])
  2578. self.assertEqual(len(results), 1)
  2579. self.assertEqual(results[0].__class__,
  2580. presubmit.OutputApi.PresubmitError)
  2581. def testRunPythonUnitTestsFailureUpload(self):
  2582. input_api = self.MockInputApi(None, False)
  2583. input_api.unittest = mock.MagicMock(unittest)
  2584. subprocess.Popen().returncode = 1 # pylint: disable=no-value-for-parameter
  2585. presubmit.sigint_handler.wait.return_value = (b'foo', None)
  2586. results = presubmit_canned_checks.RunPythonUnitTests(
  2587. input_api, presubmit.OutputApi, ['test_module'])
  2588. self.assertEqual(len(results), 1)
  2589. self.assertEqual(results[0].__class__,
  2590. presubmit.OutputApi.PresubmitNotifyResult)
  2591. self.assertEqual(
  2592. 'test_module\npyyyyython3 -m test_module (0.00s) failed\nfoo',
  2593. results[0]._message)
  2594. def testRunPythonUnitTestsFailureCommitting(self):
  2595. input_api = self.MockInputApi(None, True)
  2596. subprocess.Popen().returncode = 1 # pylint: disable=no-value-for-parameter
  2597. presubmit.sigint_handler.wait.return_value = (b'foo', None)
  2598. results = presubmit_canned_checks.RunPythonUnitTests(
  2599. input_api, presubmit.OutputApi, ['test_module'])
  2600. self.assertEqual(len(results), 1)
  2601. self.assertEqual(results[0].__class__,
  2602. presubmit.OutputApi.PresubmitError)
  2603. self.assertEqual(
  2604. 'test_module\npyyyyython3 -m test_module (0.00s) failed\nfoo',
  2605. results[0]._message)
  2606. def testRunPythonUnitTestsSuccess(self):
  2607. input_api = self.MockInputApi(None, False)
  2608. input_api.unittest = mock.MagicMock(unittest)
  2609. subprocess.Popen().returncode = 0 # pylint: disable=no-value-for-parameter
  2610. presubmit.sigint_handler.wait.return_value = (b'', None)
  2611. presubmit_canned_checks.RunPythonUnitTests(input_api,
  2612. presubmit.OutputApi,
  2613. ['test_module'])
  2614. results = input_api.thread_pool.RunAsync()
  2615. self.assertEqual(results, [])
  2616. def testCannedRunPylint(self):
  2617. change = mock.Mock()
  2618. change.RepositoryRoot.return_value = 'CWD'
  2619. input_api = self.MockInputApi(change, True)
  2620. input_api.environ = mock.MagicMock(os.environ)
  2621. input_api.environ.copy.return_value = {}
  2622. input_api.AffectedSourceFiles.return_value = True
  2623. input_api.PresubmitLocalPath.return_value = 'CWD'
  2624. input_api.os_walk.return_value = [('CWD', [], ['file1.py'])]
  2625. process = mock.Mock()
  2626. process.returncode = 0
  2627. subprocess.Popen.return_value = process
  2628. presubmit.sigint_handler.wait.return_value = (b'', None)
  2629. pylint = os.path.join(_ROOT, 'pylint-2.7')
  2630. pylintrc = os.path.join(_ROOT, 'pylintrc-2.7')
  2631. env = {str('PYTHONPATH'): str('')}
  2632. if sys.platform == 'win32':
  2633. pylint += '.bat'
  2634. results = presubmit_canned_checks.RunPylint(input_api,
  2635. presubmit.OutputApi,
  2636. version='2.7')
  2637. self.assertEqual([], results)
  2638. self.assertEqual(subprocess.Popen.mock_calls, [
  2639. mock.call([pylint, '--args-on-stdin'],
  2640. env=env,
  2641. cwd='CWD',
  2642. stderr=subprocess.STDOUT,
  2643. stdout=subprocess.PIPE,
  2644. stdin=subprocess.PIPE),
  2645. ])
  2646. self.assertEqual(presubmit.sigint_handler.wait.mock_calls, [
  2647. mock.call(process,
  2648. ('--rcfile=%s\nfile1.py' % pylintrc).encode('utf-8')),
  2649. ])
  2650. self.checkstdout('')
  2651. def GetInputApiWithFiles(self, files):
  2652. change = mock.MagicMock(presubmit.Change)
  2653. change.AffectedFiles = lambda *a, **kw: (presubmit.Change.AffectedFiles(
  2654. change, *a, **kw))
  2655. change._affected_files = []
  2656. for path, (action, contents) in files.items():
  2657. affected_file = mock.MagicMock(presubmit.GitAffectedFile)
  2658. affected_file.AbsoluteLocalPath.return_value = path
  2659. affected_file.LocalPath.return_value = path
  2660. affected_file.Action.return_value = action
  2661. affected_file.ChangedContents.return_value = [
  2662. (1, contents or ''),
  2663. ]
  2664. change._affected_files.append(affected_file)
  2665. input_api = self.MockInputApi(None, False)
  2666. input_api.change = change
  2667. input_api.ReadFile = lambda path: files[path][1]
  2668. input_api.basename = os.path.basename
  2669. input_api.is_windows = sys.platform.startswith('win')
  2670. os.path.exists = lambda path: path in files and files[path][0] != 'D'
  2671. os.path.isfile = os.path.exists
  2672. return input_api
  2673. def testCheckDirMetadataFormat(self):
  2674. input_api = self.GetInputApiWithFiles({
  2675. 'DIR_METADATA': ('M', ''),
  2676. 'a/DIR_METADATA': ('M', ''),
  2677. 'a/b/OWNERS': ('M', ''),
  2678. 'c/DIR_METADATA': ('D', ''),
  2679. 'd/unrelated': ('M', ''),
  2680. })
  2681. dirmd_bin = 'dirmd.bat' if input_api.is_windows else 'dirmd'
  2682. expected_args = [
  2683. 'validate', 'DIR_METADATA', 'a/DIR_METADATA', 'a/b/OWNERS'
  2684. ]
  2685. commands = presubmit_canned_checks.CheckDirMetadataFormat(
  2686. input_api, presubmit.OutputApi)
  2687. self.assertEqual(1, len(commands))
  2688. command = commands[0].cmd
  2689. self.assertTrue(command[0].endswith(dirmd_bin))
  2690. self.assertEqual(expected_args, command[1:])
  2691. def testCheckNoNewMetadataInOwners(self):
  2692. input_api = self.GetInputApiWithFiles({
  2693. 'no-new-metadata/OWNERS': ('M', '# WARNING: Blah'),
  2694. 'added-no-new-metadata/OWNERS': ('A', '# WARNING: Bleh'),
  2695. 'deleted/OWNERS': ('D', None),
  2696. })
  2697. self.assertEqual([],
  2698. presubmit_canned_checks.CheckNoNewMetadataInOwners(
  2699. input_api, presubmit.OutputApi))
  2700. def testCheckNoNewMetadataInOwnersFails(self):
  2701. input_api = self.GetInputApiWithFiles({
  2702. 'new-metadata/OWNERS': ('M', '# CoMpOnEnT: Monorail>Component'),
  2703. })
  2704. results = presubmit_canned_checks.CheckNoNewMetadataInOwners(
  2705. input_api, presubmit.OutputApi)
  2706. self.assertEqual(1, len(results))
  2707. self.assertIsInstance(results[0], presubmit.OutputApi.PresubmitError)
  2708. def testCheckOwnersDirMetadataExclusiveWorks(self):
  2709. input_api = self.GetInputApiWithFiles({
  2710. 'only-owners/OWNERS': ('M', '# COMPONENT: Monorail>Component'),
  2711. 'only-dir-metadata/DIR_METADATA': ('M', ''),
  2712. 'owners-has-no-metadata/DIR_METADATA': ('M', ''),
  2713. 'owners-has-no-metadata/OWNERS': ('M', 'no-metadata'),
  2714. 'deleted-owners/OWNERS': ('D', None),
  2715. 'deleted-owners/DIR_METADATA': ('M', ''),
  2716. 'deleted-dir-metadata/OWNERS':
  2717. ('M', '# COMPONENT: Monorail>Component'),
  2718. 'deleted-dir-metadata/DIR_METADATA': ('D', None),
  2719. 'non-metadata-comment/OWNERS': ('M', '# WARNING: something.'),
  2720. 'non-metadata-comment/DIR_METADATA': ('M', ''),
  2721. })
  2722. self.assertEqual(
  2723. [],
  2724. presubmit_canned_checks.CheckOwnersDirMetadataExclusive(
  2725. input_api, presubmit.OutputApi))
  2726. def testCheckOwnersDirMetadataExclusiveFails(self):
  2727. input_api = self.GetInputApiWithFiles({
  2728. 'DIR_METADATA': ('M', ''),
  2729. 'OWNERS': ('M', '# COMPONENT: Monorail>Component'),
  2730. })
  2731. results = presubmit_canned_checks.CheckOwnersDirMetadataExclusive(
  2732. input_api, presubmit.OutputApi)
  2733. self.assertEqual(1, len(results))
  2734. self.assertIsInstance(results[0], presubmit.OutputApi.PresubmitError)
  2735. def GetInputApiWithOWNERS(self, owners_content):
  2736. input_api = self.GetInputApiWithFiles({'OWNERS': ('M', owners_content)})
  2737. gerrit_mock = mock.MagicMock(presubmit.GerritAccessor)
  2738. gerrit_mock.IsCodeOwnersEnabledOnRepo = lambda: True
  2739. input_api.gerrit = gerrit_mock
  2740. return input_api
  2741. def testCheckOwnersFormatWorks_CodeOwners(self):
  2742. # If code owners is enabled, we rely on it to check owners format
  2743. # instead of depot tools.
  2744. input_api = self.GetInputApiWithOWNERS('any content')
  2745. self.assertEqual([],
  2746. presubmit_canned_checks.CheckOwnersFormat(
  2747. input_api, presubmit.OutputApi))
  2748. def AssertOwnersWorks(self,
  2749. tbr=False,
  2750. issue='1',
  2751. approvers=None,
  2752. modified_files=None,
  2753. owners_by_path=None,
  2754. is_committing=True,
  2755. response=None,
  2756. expected_output='',
  2757. manually_specified_reviewers=None,
  2758. dry_run=None):
  2759. # The set of people who lgtm'ed a change.
  2760. approvers = approvers or set()
  2761. manually_specified_reviewers = manually_specified_reviewers or []
  2762. modified_files = modified_files or ['foo/xyz.cc']
  2763. owners_by_path = owners_by_path or {'foo/xyz.cc': ['john@example.com']}
  2764. response = response or {
  2765. "owner": {
  2766. "email": 'john@example.com'
  2767. },
  2768. "labels": {
  2769. "Code-Review": {
  2770. u'all': [{
  2771. u'email': a,
  2772. u'value': +1
  2773. } for a in approvers],
  2774. u'default_value': 0,
  2775. u'values': {
  2776. u' 0': u'No score',
  2777. u'+1': u'Looks good to me',
  2778. u'-1': u"I would prefer that you didn't submit this"
  2779. }
  2780. }
  2781. },
  2782. "reviewers": {"REVIEWER": [{
  2783. u'email': a
  2784. }]
  2785. for a in approvers},
  2786. }
  2787. change = mock.MagicMock(presubmit.Change)
  2788. change.OriginalOwnersFiles.return_value = {}
  2789. change.RepositoryRoot.return_value = None
  2790. change.ReviewersFromDescription.return_value = manually_specified_reviewers
  2791. change.TBRsFromDescription.return_value = []
  2792. change.author_email = 'john@example.com'
  2793. change.issue = issue
  2794. affected_files = []
  2795. for f in modified_files:
  2796. affected_file = mock.MagicMock(presubmit.GitAffectedFile)
  2797. affected_file.LocalPath.return_value = f
  2798. affected_files.append(affected_file)
  2799. change.AffectedFiles.return_value = affected_files
  2800. input_api = self.MockInputApi(change, False)
  2801. input_api.gerrit = presubmit.GerritAccessor('host')
  2802. input_api.is_committing = is_committing
  2803. input_api.tbr = tbr
  2804. input_api.dry_run = dry_run
  2805. input_api.gerrit._FetchChangeDetail = lambda _: response
  2806. input_api.gerrit.IsCodeOwnersEnabledOnRepo = lambda: True
  2807. input_api.owners_client = owners_client.OwnersClient()
  2808. with mock.patch('owners_client.OwnersClient.ListOwners',
  2809. side_effect=lambda f: owners_by_path.get(f, [])):
  2810. results = presubmit_canned_checks.CheckOwners(
  2811. input_api, presubmit.OutputApi)
  2812. for result in results:
  2813. result.handle()
  2814. if expected_output:
  2815. self.assertRegexpMatches(sys.stdout.getvalue(), expected_output)
  2816. else:
  2817. self.assertEqual(sys.stdout.getvalue(), expected_output)
  2818. sys.stdout.truncate(0)
  2819. def testCannedCheckOwners_TBRIgnored(self):
  2820. self.AssertOwnersWorks(tbr=True, expected_output='')
  2821. self.AssertOwnersWorks(tbr=True,
  2822. is_committing=False,
  2823. expected_output='')
  2824. @mock.patch('io.open', mock.mock_open())
  2825. def testCannedRunUnitTests(self):
  2826. io.open().readline.return_value = ''
  2827. change = presubmit.Change('foo1', 'description1', self.fake_root_dir,
  2828. None, 0, 0, None)
  2829. input_api = self.MockInputApi(change, False)
  2830. input_api.verbose = True
  2831. input_api.PresubmitLocalPath.return_value = self.fake_root_dir
  2832. presubmit.sigint_handler.wait.return_value = (b'', None)
  2833. process1 = mock.Mock()
  2834. process1.returncode = 1
  2835. process2 = mock.Mock()
  2836. process2.returncode = 0
  2837. subprocess.Popen.side_effect = [process1, process2]
  2838. unit_tests = ['allo', 'bar.py']
  2839. results = presubmit_canned_checks.RunUnitTests(input_api,
  2840. presubmit.OutputApi,
  2841. unit_tests)
  2842. self.assertEqual(2, len(results))
  2843. self.assertEqual(presubmit.OutputApi.PresubmitNotifyResult,
  2844. results[1].__class__)
  2845. self.assertEqual(presubmit.OutputApi.PresubmitPromptWarning,
  2846. results[0].__class__)
  2847. cmd = ['bar.py', '--verbose']
  2848. if input_api.platform == 'win32':
  2849. cmd.insert(0, 'vpython3.bat')
  2850. else:
  2851. cmd.insert(0, 'vpython3')
  2852. self.assertEqual(subprocess.Popen.mock_calls, [
  2853. mock.call(cmd,
  2854. cwd=self.fake_root_dir,
  2855. stdout=subprocess.PIPE,
  2856. stderr=subprocess.STDOUT,
  2857. stdin=subprocess.PIPE),
  2858. mock.call(['allo', '--verbose'],
  2859. cwd=self.fake_root_dir,
  2860. stdout=subprocess.PIPE,
  2861. stderr=subprocess.STDOUT,
  2862. stdin=subprocess.PIPE),
  2863. ])
  2864. threading.Timer.assert_not_called()
  2865. self.checkstdout('')
  2866. @mock.patch('io.open', mock.mock_open())
  2867. def testCannedRunUnitTestsWithTimer(self):
  2868. io.open().readline.return_value = ''
  2869. change = presubmit.Change('foo1', 'description1', self.fake_root_dir,
  2870. None, 0, 0, None)
  2871. input_api = self.MockInputApi(change, False)
  2872. input_api.verbose = True
  2873. input_api.thread_pool.timeout = 100
  2874. input_api.PresubmitLocalPath.return_value = self.fake_root_dir
  2875. presubmit.sigint_handler.wait.return_value = (b'', None)
  2876. subprocess.Popen.return_value = mock.Mock(returncode=0)
  2877. results = presubmit_canned_checks.RunUnitTests(input_api,
  2878. presubmit.OutputApi,
  2879. ['bar.py'])
  2880. self.assertEqual(presubmit.OutputApi.PresubmitNotifyResult,
  2881. results[0].__class__)
  2882. threading.Timer.assert_called_once_with(input_api.thread_pool.timeout,
  2883. mock.ANY)
  2884. threading.Timer().start.assert_called_once_with()
  2885. threading.Timer().cancel.assert_called_once_with()
  2886. self.checkstdout('')
  2887. @mock.patch('io.open', mock.mock_open())
  2888. def testCannedRunUnitTestsWithTimerTimesOut(self):
  2889. io.open().readline.return_value = ''
  2890. change = presubmit.Change('foo1', 'description1', self.fake_root_dir,
  2891. None, 0, 0, None)
  2892. input_api = self.MockInputApi(change, False)
  2893. input_api.verbose = True
  2894. input_api.thread_pool.timeout = 100
  2895. input_api.PresubmitLocalPath.return_value = self.fake_root_dir
  2896. presubmit.sigint_handler.wait.return_value = (b'', None)
  2897. subprocess.Popen.return_value = mock.Mock(returncode=1)
  2898. timer_instance = mock.Mock()
  2899. def mockTimer(_, fn):
  2900. fn()
  2901. return timer_instance
  2902. threading.Timer.side_effect = mockTimer
  2903. results = presubmit_canned_checks.RunUnitTests(input_api,
  2904. presubmit.OutputApi,
  2905. ['bar.py'])
  2906. self.assertEqual(presubmit.OutputApi.PresubmitPromptWarning,
  2907. results[0].__class__)
  2908. results[0].handle()
  2909. self.assertIn(
  2910. 'bar.py --verbose (0.00s) failed\nProcess timed out after 100s',
  2911. sys.stdout.getvalue())
  2912. threading.Timer.assert_called_once_with(input_api.thread_pool.timeout,
  2913. mock.ANY)
  2914. timer_instance.start.assert_called_once_with()
  2915. @mock.patch('io.open', mock.mock_open())
  2916. def testCannedRunUnitTestsPython3(self):
  2917. io.open().readline.return_value = '#!/usr/bin/env python3'
  2918. change = presubmit.Change('foo1', 'description1', self.fake_root_dir,
  2919. None, 0, 0, None)
  2920. input_api = self.MockInputApi(change, False)
  2921. input_api.verbose = True
  2922. input_api.PresubmitLocalPath.return_value = self.fake_root_dir
  2923. presubmit.sigint_handler.wait.return_value = (b'', None)
  2924. subprocesses = [
  2925. mock.Mock(returncode=1),
  2926. mock.Mock(returncode=0),
  2927. mock.Mock(returncode=0),
  2928. ]
  2929. subprocess.Popen.side_effect = subprocesses
  2930. unit_tests = ['allo', 'bar.py']
  2931. results = presubmit_canned_checks.RunUnitTests(input_api,
  2932. presubmit.OutputApi,
  2933. unit_tests)
  2934. self.assertEqual([result.__class__ for result in results], [
  2935. presubmit.OutputApi.PresubmitPromptWarning,
  2936. presubmit.OutputApi.PresubmitNotifyResult,
  2937. ])
  2938. cmd = ['bar.py', '--verbose']
  2939. vpython3 = 'vpython3'
  2940. if input_api.platform == 'win32':
  2941. vpython3 += '.bat'
  2942. self.assertEqual(subprocess.Popen.mock_calls, [
  2943. mock.call([vpython3] + cmd,
  2944. cwd=self.fake_root_dir,
  2945. stdout=subprocess.PIPE,
  2946. stderr=subprocess.STDOUT,
  2947. stdin=subprocess.PIPE),
  2948. mock.call(['allo', '--verbose'],
  2949. cwd=self.fake_root_dir,
  2950. stdout=subprocess.PIPE,
  2951. stderr=subprocess.STDOUT,
  2952. stdin=subprocess.PIPE),
  2953. ])
  2954. self.assertEqual(presubmit.sigint_handler.wait.mock_calls, [
  2955. mock.call(subprocesses[0], None),
  2956. mock.call(subprocesses[1], None),
  2957. ])
  2958. self.checkstdout('')
  2959. @mock.patch('io.open', mock.mock_open())
  2960. def testCannedRunUnitTestsDontRunOnPython2(self):
  2961. io.open().readline.return_value = '#!/usr/bin/env python3'
  2962. change = presubmit.Change('foo1', 'description1', self.fake_root_dir,
  2963. None, 0, 0, None)
  2964. input_api = self.MockInputApi(change, False)
  2965. input_api.verbose = True
  2966. input_api.PresubmitLocalPath.return_value = self.fake_root_dir
  2967. presubmit.sigint_handler.wait.return_value = (b'', None)
  2968. subprocess.Popen.side_effect = [
  2969. mock.Mock(returncode=1),
  2970. mock.Mock(returncode=0),
  2971. mock.Mock(returncode=0),
  2972. ]
  2973. unit_tests = ['allo', 'bar.py']
  2974. results = presubmit_canned_checks.RunUnitTests(input_api,
  2975. presubmit.OutputApi,
  2976. unit_tests,
  2977. run_on_python2=False)
  2978. self.assertEqual([result.__class__ for result in results], [
  2979. presubmit.OutputApi.PresubmitPromptWarning,
  2980. presubmit.OutputApi.PresubmitNotifyResult,
  2981. ])
  2982. cmd = ['bar.py', '--verbose']
  2983. vpython3 = 'vpython3'
  2984. if input_api.platform == 'win32':
  2985. vpython3 += '.bat'
  2986. self.assertEqual(subprocess.Popen.mock_calls, [
  2987. mock.call([vpython3] + cmd,
  2988. cwd=self.fake_root_dir,
  2989. stdout=subprocess.PIPE,
  2990. stderr=subprocess.STDOUT,
  2991. stdin=subprocess.PIPE),
  2992. mock.call(['allo', '--verbose'],
  2993. cwd=self.fake_root_dir,
  2994. stdout=subprocess.PIPE,
  2995. stderr=subprocess.STDOUT,
  2996. stdin=subprocess.PIPE),
  2997. ])
  2998. self.checkstdout('')
  2999. def testCannedRunUnitTestsInDirectory(self):
  3000. change = presubmit.Change('foo1', 'description1', self.fake_root_dir,
  3001. None, 0, 0, None)
  3002. input_api = self.MockInputApi(change, False)
  3003. input_api.verbose = True
  3004. input_api.logging = mock.MagicMock(logging)
  3005. input_api.PresubmitLocalPath.return_value = self.fake_root_dir
  3006. input_api.os_listdir.return_value = ['.', '..', 'a', 'b', 'c']
  3007. input_api.os_path.isfile = lambda x: not x.endswith('.')
  3008. process = mock.Mock()
  3009. process.returncode = 0
  3010. subprocess.Popen.return_value = process
  3011. presubmit.sigint_handler.wait.return_value = (b'', None)
  3012. results = presubmit_canned_checks.RunUnitTestsInDirectory(
  3013. input_api,
  3014. presubmit.OutputApi,
  3015. 'random_directory',
  3016. files_to_check=['^a$', '^b$'],
  3017. files_to_skip=['a'])
  3018. self.assertEqual(1, len(results))
  3019. self.assertEqual(presubmit.OutputApi.PresubmitNotifyResult,
  3020. results[0].__class__)
  3021. self.assertEqual(subprocess.Popen.mock_calls, [
  3022. mock.call([os.path.join('random_directory', 'b'), '--verbose'],
  3023. cwd=self.fake_root_dir,
  3024. stdout=subprocess.PIPE,
  3025. stderr=subprocess.STDOUT,
  3026. stdin=subprocess.PIPE),
  3027. ])
  3028. self.checkstdout('')
  3029. def testPanProjectChecks(self):
  3030. # Make sure it accepts both list and tuples.
  3031. change = presubmit.Change('foo1', 'description1', self.fake_root_dir,
  3032. None, 0, 0, None)
  3033. input_api = self.MockInputApi(change, False)
  3034. affected_file = mock.MagicMock(presubmit.GitAffectedFile)
  3035. input_api.AffectedFiles.return_value = [affected_file]
  3036. affected_file.NewContents.return_value = 'Hey!\nHo!\nHey!\nHo!\n\n'
  3037. # CheckChangeHasNoTabs() calls _FindNewViolationsOfRule() which calls
  3038. # ChangedContents().
  3039. affected_file.ChangedContents.return_value = [
  3040. (0, 'Hey!\n'), (1, 'Ho!\n'), (2, 'Hey!\n'), (3, 'Ho!\n'), (4, '\n')
  3041. ]
  3042. affected_file.LocalPath.return_value = 'hello.py'
  3043. # CheckingLicense() calls AffectedSourceFiles() instead of
  3044. # AffectedFiles().
  3045. input_api.AffectedSourceFiles.return_value = [affected_file]
  3046. input_api.ReadFile.return_value = 'Hey!\nHo!\nHey!\nHo!\n\n'
  3047. results = presubmit_canned_checks.PanProjectChecks(input_api,
  3048. presubmit.OutputApi,
  3049. excluded_paths=None,
  3050. text_files=None,
  3051. license_header=None,
  3052. project_name=None,
  3053. owners_check=False)
  3054. self.assertEqual(2, len(results))
  3055. self.assertEqual('Found line ending with white spaces in:',
  3056. results[0]._message)
  3057. self.checkstdout('')
  3058. def testCheckCIPDManifest_file(self):
  3059. input_api = self.MockInputApi(None, False)
  3060. command = presubmit_canned_checks.CheckCIPDManifest(input_api,
  3061. presubmit.OutputApi,
  3062. path='/path/to/foo')
  3063. self.assertEqual(
  3064. command.cmd,
  3065. ['cipd', 'ensure-file-verify', '-ensure-file', '/path/to/foo'])
  3066. self.assertEqual(
  3067. command.kwargs, {
  3068. 'stdin': subprocess.PIPE,
  3069. 'stdout': subprocess.PIPE,
  3070. 'stderr': subprocess.STDOUT,
  3071. })
  3072. def testCheckCIPDManifest_content(self):
  3073. input_api = self.MockInputApi(None, False)
  3074. input_api.verbose = True
  3075. command = presubmit_canned_checks.CheckCIPDManifest(
  3076. input_api, presubmit.OutputApi, content='manifest_content')
  3077. self.assertEqual(command.cmd, [
  3078. 'cipd', 'ensure-file-verify', '-log-level', 'debug',
  3079. '-ensure-file=-'
  3080. ])
  3081. self.assertEqual(command.stdin, b'manifest_content')
  3082. self.assertEqual(
  3083. command.kwargs, {
  3084. 'stdin': subprocess.PIPE,
  3085. 'stdout': subprocess.PIPE,
  3086. 'stderr': subprocess.STDOUT,
  3087. })
  3088. def testCheckCIPDPackages(self):
  3089. content = '\n'.join([
  3090. '$VerifiedPlatform foo-bar',
  3091. '$VerifiedPlatform baz-qux',
  3092. 'foo/bar/baz/${platform} version:ohaithere',
  3093. 'qux version:kthxbye',
  3094. ])
  3095. input_api = self.MockInputApi(None, False)
  3096. command = presubmit_canned_checks.CheckCIPDPackages(
  3097. input_api,
  3098. presubmit.OutputApi,
  3099. platforms=['foo-bar', 'baz-qux'],
  3100. packages={
  3101. 'foo/bar/baz/${platform}': 'version:ohaithere',
  3102. 'qux': 'version:kthxbye',
  3103. })
  3104. self.assertEqual(command.cmd,
  3105. ['cipd', 'ensure-file-verify', '-ensure-file=-'])
  3106. self.assertEqual(command.stdin, content.encode())
  3107. self.assertEqual(
  3108. command.kwargs, {
  3109. 'stdin': subprocess.PIPE,
  3110. 'stdout': subprocess.PIPE,
  3111. 'stderr': subprocess.STDOUT,
  3112. })
  3113. def testCheckCIPDClientDigests(self):
  3114. input_api = self.MockInputApi(None, False)
  3115. input_api.verbose = True
  3116. command = presubmit_canned_checks.CheckCIPDClientDigests(
  3117. input_api, presubmit.OutputApi, client_version_file='ver')
  3118. self.assertEqual(command.cmd, [
  3119. 'cipd',
  3120. 'selfupdate-roll',
  3121. '-check',
  3122. '-version-file',
  3123. 'ver',
  3124. '-log-level',
  3125. 'debug',
  3126. ])
  3127. def testCannedCheckVPythonSpec(self):
  3128. change = presubmit.Change('a', 'b', self.fake_root_dir, None, 0, 0,
  3129. None)
  3130. input_api = self.MockInputApi(change, False)
  3131. affected_filenames = ['/path1/to/.vpython', '/path1/to/.vpython3']
  3132. affected_files = []
  3133. for filename in affected_filenames:
  3134. affected_file = mock.MagicMock(presubmit.GitAffectedFile)
  3135. affected_file.AbsoluteLocalPath.return_value = filename
  3136. affected_files.append(affected_file)
  3137. input_api.AffectedTestableFiles.return_value = affected_files
  3138. commands = presubmit_canned_checks.CheckVPythonSpec(
  3139. input_api, presubmit.OutputApi)
  3140. self.assertEqual(len(commands), len(affected_filenames))
  3141. for i in range(0, len(commands)):
  3142. self.assertEqual(commands[i].name,
  3143. 'Verify ' + affected_filenames[i])
  3144. self.assertEqual(commands[i].cmd, [
  3145. input_api.python3_executable, '-vpython-spec',
  3146. affected_filenames[i], '-vpython-tool', 'verify'
  3147. ])
  3148. self.assertDictEqual(
  3149. commands[0].kwargs, {
  3150. 'stderr': input_api.subprocess.STDOUT,
  3151. 'stdout': input_api.subprocess.PIPE,
  3152. 'stdin': input_api.subprocess.PIPE,
  3153. })
  3154. self.assertEqual(commands[0].message,
  3155. presubmit.OutputApi.PresubmitError)
  3156. self.assertIsNone(commands[0].info)
  3157. class ThreadPoolTest(unittest.TestCase):
  3158. def setUp(self):
  3159. super(ThreadPoolTest, self).setUp()
  3160. mock.patch('subprocess2.Popen').start()
  3161. mock.patch('presubmit_support.sigint_handler').start()
  3162. mock.patch('presubmit_support.time_time', return_value=0).start()
  3163. presubmit.sigint_handler.wait.return_value = (b'stdout', '')
  3164. self.addCleanup(mock.patch.stopall)
  3165. def testSurfaceExceptions(self):
  3166. def FakePopen(cmd, **kwargs):
  3167. if cmd[0] == '3':
  3168. raise TypeError('TypeError')
  3169. if cmd[0] == '4':
  3170. raise OSError('OSError')
  3171. if cmd[0] == '5':
  3172. return mock.Mock(returncode=1)
  3173. return mock.Mock(returncode=0)
  3174. subprocess.Popen.side_effect = FakePopen
  3175. mock_tests = [
  3176. presubmit.CommandData(
  3177. name=str(i),
  3178. cmd=[str(i)],
  3179. kwargs={},
  3180. message=lambda x, **kwargs: x,
  3181. ) for i in range(10)
  3182. ]
  3183. t = presubmit.ThreadPool(1)
  3184. t.AddTests(mock_tests)
  3185. messages = sorted(t.RunAsync())
  3186. self.assertEqual(3, len(messages))
  3187. self.assertIn(
  3188. '3\n3 exec failure (0.00s)\nTraceback (most recent call last):',
  3189. messages[0])
  3190. self.assertIn(
  3191. '4\n4 exec failure (0.00s)\nTraceback (most recent call last):',
  3192. messages[1])
  3193. self.assertEqual('5\n5 (0.00s) failed\nstdout', messages[2])
  3194. if __name__ == '__main__':
  3195. import unittest
  3196. unittest.main()