update_cts_test.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. #!/usr/bin/env vpython3
  2. # Copyright 2020 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. import json
  6. import os
  7. import sys
  8. import unittest
  9. import zipfile
  10. import six
  11. import mock # pylint: disable=import-error
  12. from mock import call # pylint: disable=import-error
  13. from mock import patch # pylint: disable=import-error
  14. sys.path.append(
  15. os.path.join(
  16. os.path.dirname(__file__), os.pardir, os.pardir, 'third_party',
  17. 'catapult', 'common', 'py_utils'))
  18. # pylint: disable=wrong-import-position,import-error
  19. from py_utils import tempfile_ext
  20. import update_cts
  21. import cts_utils
  22. import cts_utils_test
  23. from cts_utils_test import CIPD_DATA, CONFIG_DATA, DEPS_DATA
  24. from cts_utils_test import GENERATE_BUILDBOT_JSON
  25. def generate_zip_file(path, *files):
  26. """Create a zip file containing a list of files.
  27. Args:
  28. path: Path to generated zip file
  29. files: one or more file entries in the zip file,
  30. contents of which will be the same as the file's name
  31. """
  32. path_dir = os.path.dirname(path)
  33. if path_dir and not os.path.isdir(path_dir):
  34. os.makedirs(path_dir)
  35. with zipfile.ZipFile(path, 'w') as zf:
  36. for f in files:
  37. zf.writestr(f, f)
  38. def verify_zip_file(path, *files):
  39. """Verify zip file that was generated.
  40. Args:
  41. path: Path to generated zip file
  42. files: one or more file entries in the zip file,
  43. contents of which should be the same as the file entry
  44. """
  45. with zipfile.ZipFile(path) as zf:
  46. names = zf.namelist()
  47. if len(files) != len(names):
  48. raise AssertionError('Expected ' + len(files) + ' files, found ' +
  49. len(names) + '.')
  50. for f in files:
  51. if f not in names:
  52. raise AssertionError(f + ' should be in zip file.')
  53. s = zf.read(f)
  54. if six.ensure_str(s) != f:
  55. raise AssertionError('Expected ' + f + ', found ' + s)
  56. class FakeDownload:
  57. """Allows test to simulate downloads of CTS zip files."""
  58. def __init__(self):
  59. self._files = {}
  60. def add_fake_zip_files(self, cts_config_json):
  61. """Associate generated zip files to origin urls in config.
  62. Each origin url will be associated with a zip file, contents of which will
  63. be apks specified for that platform. Contents of each apk will just be the
  64. path of that apk as a string.
  65. Args:
  66. cts_config_json: config json string
  67. """
  68. config = json.loads(cts_config_json)
  69. for p in config:
  70. for a in config[p]['arch']:
  71. o = config[p]['arch'][a]['_origin']
  72. for test_run in config[p]['test_runs']:
  73. self.append_to_zip_file(o, test_run['apk'])
  74. for additional_apk in test_run.get('additional_apks', []):
  75. self.append_to_zip_file(o, additional_apk['apk'])
  76. def append_to_zip_file(self, url, file_name):
  77. """Append files to any zip files associated with the url.
  78. If no zip files are associated with the url, one will be created first.
  79. Contents of each file_name will just be the path of that apk as a string.
  80. If file_name is already associated with the url, then this is a no-op.
  81. Args:
  82. url: Url to associate
  83. file_name: Path to add to the zip file associated with the url
  84. """
  85. if url not in self._files:
  86. self._files[url] = []
  87. if file_name not in self._files[url]:
  88. self._files[url].append(file_name)
  89. def download(self, url, dest):
  90. if url not in self._files:
  91. raise AssertionError('Url should be found: ' + url)
  92. cts_utils_test.check_tempdir(dest)
  93. with zipfile.ZipFile(dest, mode='w') as zf:
  94. for file_name in self._files[url]:
  95. zf.writestr(file_name, file_name)
  96. class UpdateCTSTest(unittest.TestCase):
  97. """Unittests for update_cts.py."""
  98. @patch('cts_utils.download')
  99. def testDownloadCTS_onePlatform(self, download_mock):
  100. with tempfile_ext.NamedTemporaryDirectory() as workDir,\
  101. tempfile_ext.NamedTemporaryDirectory() as repoRoot:
  102. cts_utils_test.setup_fake_repo(repoRoot)
  103. cts_updater = update_cts.UpdateCTS(workDir, repoRoot)
  104. self.assertEqual(os.path.join(workDir, 'downloaded'),
  105. cts_updater.download_dir)
  106. cts_updater.download_cts(platforms=['platform1'])
  107. download_mock.assert_has_calls([
  108. call(CONFIG_DATA['origin11'],
  109. os.path.join(cts_updater.download_dir, CONFIG_DATA['base11'])),
  110. call(CONFIG_DATA['origin12'],
  111. os.path.join(cts_updater.download_dir, CONFIG_DATA['base12']))
  112. ])
  113. self.assertEqual(2, download_mock.call_count)
  114. @patch('cts_utils.download')
  115. def testDownloadCTS_allPlatforms(self, download_mock):
  116. with tempfile_ext.NamedTemporaryDirectory() as workDir,\
  117. tempfile_ext.NamedTemporaryDirectory() as repoRoot:
  118. cts_utils_test.setup_fake_repo(repoRoot)
  119. cts_updater = update_cts.UpdateCTS(workDir, repoRoot)
  120. cts_updater.download_cts()
  121. download_mock.assert_has_calls([
  122. call(CONFIG_DATA['origin11'],
  123. os.path.join(cts_updater.download_dir, CONFIG_DATA['base11'])),
  124. call(CONFIG_DATA['origin12'],
  125. os.path.join(cts_updater.download_dir, CONFIG_DATA['base12'])),
  126. call(CONFIG_DATA['origin21'],
  127. os.path.join(cts_updater.download_dir, CONFIG_DATA['base21'])),
  128. call(CONFIG_DATA['origin22'],
  129. os.path.join(cts_updater.download_dir, CONFIG_DATA['base22']))
  130. ])
  131. self.assertEqual(4, download_mock.call_count)
  132. def testFilterCTS(self):
  133. with tempfile_ext.NamedTemporaryDirectory() as workDir,\
  134. tempfile_ext.NamedTemporaryDirectory() as repoRoot,\
  135. cts_utils.chdir(workDir):
  136. cts_utils_test.setup_fake_repo(repoRoot)
  137. cts_updater = update_cts.UpdateCTS('.', repoRoot)
  138. expected_download_dir = os.path.abspath('downloaded')
  139. self.assertEqual(expected_download_dir, cts_updater.download_dir)
  140. os.makedirs(expected_download_dir)
  141. with cts_utils.chdir('downloaded'):
  142. generate_zip_file(CONFIG_DATA['base11'], CONFIG_DATA['apk1'],
  143. 'not/a/webview/apk')
  144. generate_zip_file(CONFIG_DATA['base12'], CONFIG_DATA['apk1'],
  145. 'not/a/webview/apk')
  146. cts_updater.filter_downloaded_cts()
  147. with cts_utils.chdir('filtered'):
  148. self.assertEqual(2, len(os.listdir('.')))
  149. verify_zip_file(CONFIG_DATA['base11'], CONFIG_DATA['apk1'])
  150. verify_zip_file(CONFIG_DATA['base12'], CONFIG_DATA['apk1'])
  151. @patch('devil.utils.cmd_helper.RunCmd')
  152. @unittest.skipIf(os.name == "nt", "This fails on Windows because it calls "
  153. "download_cipd which ultimately calls cipd_ensure which "
  154. "creates a file with NamedTemporaryFile and then opens it "
  155. "by name, which hits permission errors.")
  156. def testDownloadCIPD(self, run_mock):
  157. with tempfile_ext.NamedTemporaryDirectory() as workDir,\
  158. tempfile_ext.NamedTemporaryDirectory() as repoRoot,\
  159. cts_utils.chdir(workDir):
  160. cts_utils_test.setup_fake_repo(repoRoot)
  161. fake_cipd = cts_utils_test.FakeCIPD()
  162. fake_cipd.add_package(
  163. os.path.join(repoRoot, cts_utils.TOOLS_DIR, cts_utils.CIPD_FILE),
  164. DEPS_DATA['revision'])
  165. fake_run_cmd = cts_utils_test.FakeRunCmd(fake_cipd)
  166. run_mock.side_effect = fake_run_cmd.run_cmd
  167. cts_updater = update_cts.UpdateCTS('.', repoRoot)
  168. cts_updater.download_cipd()
  169. self.assertTrue(os.path.isdir('cipd'))
  170. for i in [str(x) for x in range(1, 5)]:
  171. self.assertEqual(
  172. CIPD_DATA['file' + i],
  173. cts_utils_test.readfile(
  174. os.path.join(workDir, 'cipd', CIPD_DATA['file' + i])))
  175. @unittest.skipIf(os.name == "nt", "This fails on Windows because it calls "
  176. "download_cipd which ultimately calls cipd_ensure which "
  177. "creates a file with NamedTemporaryFile and then opens it "
  178. "by name, which hits permission errors.")
  179. def testDownloadCIPD_dirExists(self):
  180. with tempfile_ext.NamedTemporaryDirectory() as workDir,\
  181. tempfile_ext.NamedTemporaryDirectory() as repoRoot,\
  182. cts_utils.chdir(workDir):
  183. cts_utils_test.setup_fake_repo(repoRoot)
  184. cts_updater = update_cts.UpdateCTS('.', repoRoot)
  185. os.makedirs('cipd')
  186. with self.assertRaises(update_cts.DirExistsError):
  187. cts_updater.download_cipd()
  188. def testStageCIPDUpdate(self):
  189. with tempfile_ext.NamedTemporaryDirectory() as workDir,\
  190. tempfile_ext.NamedTemporaryDirectory() as repoRoot,\
  191. cts_utils.chdir(workDir):
  192. cts_utils_test.setup_fake_repo(repoRoot)
  193. cts_utils_test.writefile('n1',
  194. os.path.join('filtered', CONFIG_DATA['base11']))
  195. cts_utils_test.writefile('n3',
  196. os.path.join('filtered', CONFIG_DATA['base12']))
  197. for i in [str(i) for i in range(1, 5)]:
  198. cts_utils_test.writefile('o' + i,
  199. os.path.join('cipd', CIPD_DATA['file' + i]))
  200. cts_updater = update_cts.UpdateCTS('.', repoRoot)
  201. cts_updater.stage_cipd_update()
  202. self.assertTrue(os.path.isdir('staged'))
  203. with cts_utils.chdir('staged'):
  204. self.assertEqual('n1', cts_utils_test.readfile(CONFIG_DATA['file11']))
  205. self.assertEqual('n3', cts_utils_test.readfile(CONFIG_DATA['file12']))
  206. self.assertEqual('o2', cts_utils_test.readfile(CONFIG_DATA['file21']))
  207. self.assertEqual('o4', cts_utils_test.readfile(CONFIG_DATA['file22']))
  208. self.assertEqual(CIPD_DATA['yaml'],
  209. cts_utils_test.readfile('cipd.yaml'))
  210. @patch('devil.utils.cmd_helper.GetCmdOutput')
  211. def testUpdateCtsConfigFileOrigins(self, cmd_mock):
  212. with tempfile_ext.NamedTemporaryDirectory() as workDir,\
  213. tempfile_ext.NamedTemporaryDirectory() as repoRoot,\
  214. cts_utils.chdir(workDir):
  215. cmd_mock.return_value = """
  216. hash refs/tags/platform-1.0_r6
  217. hash refs/tags/platform-1.0_r7
  218. hash refs/tags/platform-1.0_r9
  219. hash refs/tags/platform-2.0_r2
  220. hash refs/tags/platform-2.0_r3
  221. """
  222. expected_config_file = json.loads(CONFIG_DATA['json'])
  223. expected_config_file['platform1']['arch']['arch1'][
  224. 'unzip_dir'] = 'arch1/path/platform1_r9'
  225. expected_config_file['platform1']['arch']['arch2'][
  226. 'unzip_dir'] = 'arch1/path/platform1_r9'
  227. expected_config_file['platform2']['arch']['arch1'][
  228. 'unzip_dir'] = 'arch1/path/platform2_r3'
  229. expected_config_file['platform2']['arch']['arch2'][
  230. 'unzip_dir'] = 'arch1/path/platform2_r3'
  231. cts_utils_test.setup_fake_repo(repoRoot)
  232. cts_updater = update_cts.UpdateCTS('.', repoRoot)
  233. cts_updater.update_cts_download_origins_cmd()
  234. with cts_utils.chdir(repoRoot):
  235. actual_config_file = json.loads(
  236. cts_utils_test.readfile(
  237. os.path.join(cts_utils.TOOLS_DIR, cts_utils.CONFIG_FILE)))
  238. self.assertEqual(expected_config_file, actual_config_file)
  239. @patch('cts_utils.update_cipd_package')
  240. def testCommitStagedCIPD(self, update_mock):
  241. with tempfile_ext.NamedTemporaryDirectory() as workDir,\
  242. tempfile_ext.NamedTemporaryDirectory() as repoRoot,\
  243. cts_utils.chdir(workDir):
  244. cts_utils_test.setup_fake_repo(repoRoot)
  245. cts_updater = update_cts.UpdateCTS('.', repoRoot)
  246. with self.assertRaises(update_cts.MissingDirError):
  247. cts_updater.commit_staged_cipd()
  248. cts_utils_test.writefile(CIPD_DATA['yaml'],
  249. os.path.join('staged', 'cipd.yaml'))
  250. with cts_utils.chdir('staged'):
  251. generate_zip_file(CONFIG_DATA['file11'], CONFIG_DATA['apk1'])
  252. generate_zip_file(CONFIG_DATA['file12'], CONFIG_DATA['apk1'])
  253. generate_zip_file(CONFIG_DATA['file21'], CONFIG_DATA['apk2a'],
  254. CONFIG_DATA['apk2b'])
  255. with self.assertRaises(update_cts.MissingFileError):
  256. cts_updater.commit_staged_cipd()
  257. generate_zip_file(CONFIG_DATA['file22'], CONFIG_DATA['apk2a'],
  258. CONFIG_DATA['apk2b'])
  259. update_mock.return_value = 'newcipdversion'
  260. cts_updater.commit_staged_cipd()
  261. update_mock.assert_called_with(
  262. os.path.join(workDir, 'staged', 'cipd.yaml'))
  263. self.assertEqual('newcipdversion',
  264. cts_utils_test.readfile('cipd_version.txt'))
  265. @patch('devil.utils.cmd_helper.RunCmd')
  266. @patch('devil.utils.cmd_helper.GetCmdOutput')
  267. @unittest.skipIf(os.name == "nt", "This fails on Windows because it calls "
  268. "update_repository which calls cipd_ensure which creates a "
  269. "file with NamedTemporaryFile and then opens it by name, "
  270. "which hits permission errors.")
  271. def testUpdateRepository(self, cmd_mock, run_mock):
  272. with tempfile_ext.NamedTemporaryDirectory() as workDir,\
  273. tempfile_ext.NamedTemporaryDirectory() as repoRoot,\
  274. cts_utils.chdir(workDir):
  275. cts_utils_test.setup_fake_repo(repoRoot)
  276. cts_updater = update_cts.UpdateCTS('.', repoRoot)
  277. cts_utils_test.writefile('newversion', 'cipd_version.txt')
  278. cts_utils_test.writefile(CIPD_DATA['yaml'],
  279. os.path.join('staged', 'cipd.yaml'))
  280. cts_utils_test.writefile(
  281. CIPD_DATA['yaml'], os.path.join(os.path.join('staged', 'cipd.yaml')))
  282. cmd_mock.return_value = ''
  283. run_mock.return_value = 0
  284. cts_updater.update_repository()
  285. self._assertCIPDVersionUpdated(repoRoot, 'newversion')
  286. repo_cipd_yaml = os.path.join(repoRoot, cts_utils.TOOLS_DIR,
  287. cts_utils.CIPD_FILE)
  288. run_mock.assert_any_call(
  289. ['cp',
  290. os.path.join(workDir, 'staged', 'cipd.yaml'), repo_cipd_yaml])
  291. run_mock.assert_any_call([
  292. 'cipd', 'ensure', '-root',
  293. os.path.dirname(repo_cipd_yaml), '-ensure-file', mock.ANY
  294. ])
  295. run_mock.assert_any_call(['python', GENERATE_BUILDBOT_JSON])
  296. @patch('devil.utils.cmd_helper.RunCmd')
  297. @patch('devil.utils.cmd_helper.GetCmdOutput')
  298. def testUpdateRepository_uncommitedChanges(self, cmd_mock, run_mock):
  299. with tempfile_ext.NamedTemporaryDirectory() as workDir,\
  300. tempfile_ext.NamedTemporaryDirectory() as repoRoot,\
  301. cts_utils.chdir(workDir):
  302. cts_utils_test.setup_fake_repo(repoRoot)
  303. cts_updater = update_cts.UpdateCTS('.', repoRoot)
  304. cts_utils_test.writefile('newversion', 'cipd_version.txt')
  305. cts_utils_test.writefile(CIPD_DATA['yaml'],
  306. os.path.join('staged', 'cipd.yaml'))
  307. cmd_mock.return_value = 'M DEPS'
  308. run_mock.return_value = 0
  309. with self.assertRaises(update_cts.UncommittedChangeException):
  310. cts_updater.update_repository()
  311. @patch('devil.utils.cmd_helper.RunCmd')
  312. @patch('devil.utils.cmd_helper.GetCmdOutput')
  313. def testUpdateRepository_buildbotUpdateError(self, cmd_mock, run_mock):
  314. with tempfile_ext.NamedTemporaryDirectory() as workDir,\
  315. tempfile_ext.NamedTemporaryDirectory() as repoRoot,\
  316. cts_utils.chdir(workDir):
  317. cts_utils_test.setup_fake_repo(repoRoot)
  318. cts_updater = update_cts.UpdateCTS('.', repoRoot)
  319. cts_utils_test.writefile('newversion', 'cipd_version.txt')
  320. cts_utils_test.writefile(CIPD_DATA['yaml'],
  321. os.path.join('staged', 'cipd.yaml'))
  322. cmd_mock.return_value = ''
  323. run_mock.return_value = 1
  324. with self.assertRaises(IOError):
  325. cts_updater.update_repository()
  326. @patch('devil.utils.cmd_helper.RunCmd')
  327. @patch('devil.utils.cmd_helper.GetCmdOutput')
  328. def testUpdateRepository_inconsistentFiles(self, cmd_mock, run_mock):
  329. with tempfile_ext.NamedTemporaryDirectory() as workDir,\
  330. tempfile_ext.NamedTemporaryDirectory() as repoRoot,\
  331. cts_utils.chdir(workDir):
  332. cts_utils_test.setup_fake_repo(repoRoot)
  333. cts_updater = update_cts.UpdateCTS('.', repoRoot)
  334. cts_utils_test.writefile('newversion', 'cipd_version.txt')
  335. cts_utils_test.writefile(CIPD_DATA['yaml'],
  336. os.path.join('staged', 'cipd.yaml'))
  337. cmd_mock.return_value = ''
  338. run_mock.return_value = 0
  339. cts_utils_test.writefile(
  340. CIPD_DATA['template'] %
  341. ('wrong/package/name', CIPD_DATA['file1'], CIPD_DATA['file2'],
  342. CIPD_DATA['file3'], CIPD_DATA['file4']),
  343. os.path.join(os.path.join('staged', 'cipd.yaml')))
  344. with self.assertRaises(update_cts.InconsistentFilesException):
  345. cts_updater.update_repository()
  346. @patch('devil.utils.cmd_helper.RunCmd')
  347. @patch('devil.utils.cmd_helper.GetCmdOutput')
  348. @patch.object(cts_utils.ChromiumRepoHelper, 'update_testing_json')
  349. @patch('urllib.urlretrieve' if six.PY2 else 'urllib.request.urlretrieve')
  350. @unittest.skipIf(os.name == "nt", "This fails on Windows because it calls "
  351. "create_cipd_cmd which calls download_cipd which ultimately "
  352. "calls cipd_ensure which creates a file with "
  353. "NamedTemporaryFile and then opens it by name, which hits "
  354. "permission errors.")
  355. def testCompleteUpdate(self, retrieve_mock, update_json_mock, cmd_mock,
  356. run_mock):
  357. with tempfile_ext.NamedTemporaryDirectory() as workDir,\
  358. tempfile_ext.NamedTemporaryDirectory() as repoRoot,\
  359. cts_utils.chdir(workDir):
  360. cts_utils_test.setup_fake_repo(repoRoot)
  361. fake_cipd = cts_utils_test.FakeCIPD()
  362. fake_cipd.add_package(
  363. os.path.join(repoRoot, cts_utils.TOOLS_DIR, cts_utils.CIPD_FILE),
  364. DEPS_DATA['revision'])
  365. fake_download = FakeDownload()
  366. fake_download.add_fake_zip_files(CONFIG_DATA['json'])
  367. fake_run_cmd = cts_utils_test.FakeRunCmd(fake_cipd)
  368. retrieve_mock.side_effect = fake_download.download
  369. run_mock.side_effect = fake_run_cmd.run_cmd
  370. update_json_mock.return_value = 0
  371. cmd_mock.return_value = ''
  372. cts_updater = update_cts.UpdateCTS('.', repoRoot)
  373. cts_updater.download_cts_cmd()
  374. cts_updater.create_cipd_cmd()
  375. cts_updater.update_repository_cmd()
  376. latest_version = fake_cipd.get_latest_version(
  377. 'chromium/android_webview/tools/cts_archive')
  378. self.assertNotEqual(DEPS_DATA['revision'], latest_version)
  379. self._assertCIPDVersionUpdated(repoRoot, latest_version)
  380. repo_cipd_yaml = os.path.join(repoRoot, cts_utils.TOOLS_DIR,
  381. cts_utils.CIPD_FILE)
  382. run_mock.assert_any_call(
  383. ['cp',
  384. os.path.join(workDir, 'staged', 'cipd.yaml'), repo_cipd_yaml])
  385. run_mock.assert_any_call([
  386. 'cipd', 'ensure', '-root',
  387. os.path.dirname(repo_cipd_yaml), '-ensure-file', mock.ANY
  388. ])
  389. update_json_mock.assert_called_with()
  390. def _assertCIPDVersionUpdated(self, repo_root, new_version):
  391. """Check that cts cipd version in DEPS and test suites were updated.
  392. Args:
  393. repo_root: Root directory of checkout
  394. new_version: Expected version of CTS package
  395. Raises:
  396. AssertionError: If contents of DEPS and test suite files were not
  397. expected.
  398. """
  399. self.assertEqual(
  400. DEPS_DATA['template'] % (CIPD_DATA['package'], new_version),
  401. cts_utils_test.readfile(os.path.join(repo_root, 'DEPS')))
  402. self.assertEqual(
  403. cts_utils_test.SUITES_DATA['template'] % (new_version, new_version),
  404. cts_utils_test.readfile(
  405. os.path.join(repo_root, 'testing', 'buildbot', 'test_suites.pyl')))
  406. if __name__ == '__main__':
  407. unittest.main()