queries_unittest.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  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 copy
  6. import json
  7. import subprocess
  8. import sys
  9. from typing import List, Tuple
  10. import unittest
  11. import unittest.mock as mock
  12. from unexpected_passes_common import builders
  13. from unexpected_passes_common import constants
  14. from unexpected_passes_common import data_types
  15. from unexpected_passes_common import multiprocessing_utils
  16. from unexpected_passes_common import queries
  17. from unexpected_passes_common import unittest_utils
  18. queries.QUERY_DELAY = 0
  19. class HelperMethodUnittest(unittest.TestCase):
  20. def testStripPrefixFromBuildIdValidId(self) -> None:
  21. self.assertEqual(queries._StripPrefixFromBuildId('build-1'), '1')
  22. def testStripPrefixFromBuildIdInvalidId(self) -> None:
  23. with self.assertRaises(AssertionError):
  24. queries._StripPrefixFromBuildId('build1')
  25. with self.assertRaises(AssertionError):
  26. queries._StripPrefixFromBuildId('build-1-2')
  27. def testConvertActualResultToExpectationFileFormatAbort(self) -> None:
  28. self.assertEqual(
  29. queries._ConvertActualResultToExpectationFileFormat('ABORT'), 'Timeout')
  30. class QueryGeneratorUnittest(unittest.TestCase):
  31. def setUp(self):
  32. self._builder = data_types.BuilderEntry('ci', constants.BuilderTypes.CI,
  33. False)
  34. def testSplitQueryGeneratorInitialSplit(self) -> None:
  35. """Tests that initial query splitting works as expected."""
  36. test_filter = queries.SplitQueryGenerator(self._builder, ['1', '2', '3'], 2)
  37. self.assertEqual(test_filter._test_id_lists, [['1', '2'], ['3']])
  38. self.assertEqual(len(test_filter.GetClauses()), 2)
  39. test_filter = queries.SplitQueryGenerator(self._builder, ['1', '2', '3'], 3)
  40. self.assertEqual(test_filter._test_id_lists, [['1', '2', '3']])
  41. self.assertEqual(len(test_filter.GetClauses()), 1)
  42. def testSplitQueryGeneratorSplitQuery(self) -> None:
  43. """Tests that SplitQueryGenerator's query splitting works."""
  44. test_filter = queries.SplitQueryGenerator(self._builder, ['1', '2'], 10)
  45. self.assertEqual(len(test_filter.GetClauses()), 1)
  46. test_filter.SplitQuery()
  47. self.assertEqual(len(test_filter.GetClauses()), 2)
  48. def testSplitQueryGeneratorSplitQueryCannotSplitFurther(self) -> None:
  49. """Tests that SplitQueryGenerator's failure mode."""
  50. test_filter = queries.SplitQueryGenerator(self._builder, ['1'], 1)
  51. with self.assertRaises(queries.QuerySplitError):
  52. test_filter.SplitQuery()
  53. class QueryBuilderUnittest(unittest.TestCase):
  54. def setUp(self) -> None:
  55. self._patcher = mock.patch.object(subprocess, 'Popen')
  56. self._popen_mock = self._patcher.start()
  57. self.addCleanup(self._patcher.stop)
  58. builders.ClearInstance()
  59. unittest_utils.RegisterGenericBuildersImplementation()
  60. self._querier = unittest_utils.CreateGenericQuerier()
  61. self._relevant_file_patcher = mock.patch.object(
  62. self._querier,
  63. '_GetRelevantExpectationFilesForQueryResult',
  64. return_value=None)
  65. self._relevant_file_mock = self._relevant_file_patcher.start()
  66. self.addCleanup(self._relevant_file_patcher.stop)
  67. self._builder = data_types.BuilderEntry('builder',
  68. constants.BuilderTypes.CI, False)
  69. def testQueryFailureRaised(self) -> None:
  70. """Tests that a query failure is properly surfaced."""
  71. self._popen_mock.return_value = unittest_utils.FakeProcess(returncode=1)
  72. with self.assertRaises(RuntimeError):
  73. self._querier.QueryBuilder(
  74. data_types.BuilderEntry('builder', constants.BuilderTypes.CI, False))
  75. def testInvalidNumSamples(self) -> None:
  76. """Tests that the number of samples is validated."""
  77. with self.assertRaises(AssertionError):
  78. unittest_utils.CreateGenericQuerier(num_samples=-1)
  79. def testNoResults(self) -> None:
  80. """Tests functionality if the query returns no results."""
  81. self._popen_mock.return_value = unittest_utils.FakeProcess(stdout='[]')
  82. results, expectation_files = self._querier.QueryBuilder(
  83. data_types.BuilderEntry('builder', constants.BuilderTypes.CI, False))
  84. self.assertEqual(results, [])
  85. self.assertIsNone(expectation_files, None)
  86. def testValidResults(self) -> None:
  87. """Tests functionality when valid results are returned."""
  88. self._relevant_file_mock.return_value = ['foo_expectations']
  89. query_results = [
  90. {
  91. 'id':
  92. 'build-1234',
  93. 'test_id': ('ninja://chrome/test:telemetry_gpu_integration_test/'
  94. 'gpu_tests.pixel_integration_test.'
  95. 'PixelIntegrationTest.test_name'),
  96. 'status':
  97. 'FAIL',
  98. 'typ_expectations': [
  99. 'RetryOnFailure',
  100. ],
  101. 'typ_tags': [
  102. 'win',
  103. 'intel',
  104. ],
  105. 'step_name':
  106. 'step_name',
  107. },
  108. ]
  109. self._popen_mock.return_value = unittest_utils.FakeProcess(
  110. stdout=json.dumps(query_results))
  111. results, expectation_files = self._querier.QueryBuilder(
  112. data_types.BuilderEntry('builder', constants.BuilderTypes.CI, False))
  113. self.assertEqual(len(results), 1)
  114. self.assertEqual(
  115. results[0],
  116. data_types.Result('test_name', ['win', 'intel'], 'Failure', 'step_name',
  117. '1234'))
  118. self.assertEqual(expectation_files, ['foo_expectations'])
  119. def testValidResultsNoneExpectations(self) -> None:
  120. """Tests when an implementation uses None for expectation files."""
  121. query_results = [
  122. {
  123. 'id':
  124. 'build-1234',
  125. 'test_id': ('ninja://chrome/test:telemetry_gpu_integration_test/'
  126. 'gpu_tests.pixel_integration_test.'
  127. 'PixelIntegrationTest.test_name'),
  128. 'status':
  129. 'FAIL',
  130. 'typ_expectations': [
  131. 'RetryOnFailure',
  132. ],
  133. 'typ_tags': [
  134. 'win',
  135. 'intel',
  136. ],
  137. 'step_name':
  138. 'step_name',
  139. },
  140. {
  141. 'id':
  142. 'build-1234',
  143. 'test_id': ('ninja://chrome/test:telemetry_gpu_integration_test/'
  144. 'gpu_tests.pixel_integration_test.'
  145. 'PixelIntegrationTest.test_name'),
  146. 'status':
  147. 'FAIL',
  148. 'typ_expectations': [
  149. 'RetryOnFailure',
  150. ],
  151. 'typ_tags': [
  152. 'win',
  153. 'nvidia',
  154. ],
  155. 'step_name':
  156. 'step_name',
  157. },
  158. ]
  159. self._popen_mock.return_value = unittest_utils.FakeProcess(
  160. stdout=json.dumps(query_results))
  161. with mock.patch.object(
  162. self._querier, '_GetRelevantExpectationFilesForQueryResult') as ef_mock:
  163. ef_mock.return_value = None
  164. results, expectation_files = self._querier.QueryBuilder(
  165. data_types.BuilderEntry('builder', constants.BuilderTypes.CI, False))
  166. self.assertEqual(len(results), 2)
  167. self.assertIn(
  168. data_types.Result('test_name', ['win', 'intel'], 'Failure',
  169. 'step_name', '1234'), results)
  170. self.assertIn(
  171. data_types.Result('test_name', ['win', 'nvidia'], 'Failure',
  172. 'step_name', '1234'), results)
  173. self.assertIsNone(expectation_files)
  174. ef_mock.assert_called_once()
  175. def testValidResultsMultipleSteps(self) -> None:
  176. """Tests functionality when results from multiple steps are present."""
  177. def SideEffect(result: queries.QueryResult) -> List[str]:
  178. if result['step_name'] == 'a step name':
  179. return ['foo_expectations']
  180. if result['step_name'] == 'another step name':
  181. return ['bar_expectations']
  182. raise RuntimeError('Unknown step %s' % result['step_name'])
  183. self._relevant_file_mock.side_effect = SideEffect
  184. query_results = [
  185. {
  186. 'id': 'build-1234',
  187. 'test_id': 'ninja://:blink_web_tests/some/test/with.test_name',
  188. 'status': 'FAIL',
  189. 'typ_expectations': [
  190. 'Failure',
  191. ],
  192. 'typ_tags': [
  193. 'linux',
  194. 'release',
  195. ],
  196. 'step_name': 'a step name',
  197. },
  198. {
  199. 'id': 'build-1234',
  200. 'test_id': 'ninja://:blink_web_tests/some/test/with.test_name',
  201. 'status': 'FAIL',
  202. 'typ_expectations': [
  203. 'Crash',
  204. ],
  205. 'typ_tags': [
  206. 'linux',
  207. 'debug',
  208. ],
  209. 'step_name': 'another step name',
  210. },
  211. ]
  212. self._popen_mock.return_value = unittest_utils.FakeProcess(
  213. stdout=json.dumps(query_results))
  214. results, expectation_files = self._querier.QueryBuilder(
  215. data_types.BuilderEntry('builder', constants.BuilderTypes.CI, False))
  216. self.assertEqual(len(results), 2)
  217. self.assertIn(
  218. data_types.Result('test_name', ['linux', 'release'], 'Failure',
  219. 'a step name', '1234'), results)
  220. self.assertIn(
  221. data_types.Result('test_name', ['linux', 'debug'], 'Failure',
  222. 'another step name', '1234'), results)
  223. self.assertEqual(len(expectation_files), 2)
  224. self.assertEqual(set(expectation_files),
  225. set(['foo_expectations', 'bar_expectations']))
  226. def testFilterInsertion(self) -> None:
  227. """Tests that test filters are properly inserted into the query."""
  228. with mock.patch.object(
  229. self._querier,
  230. '_GetQueryGeneratorForBuilder',
  231. return_value=unittest_utils.SimpleFixedQueryGenerator(
  232. self._builder, 'a real filter')), mock.patch.object(
  233. self._querier,
  234. '_RunBigQueryCommandsForJsonOutput') as query_mock:
  235. self._querier.QueryBuilder(self._builder)
  236. query_mock.assert_called_once()
  237. query = query_mock.call_args[0][0][0]
  238. self.assertIn('a real filter', query)
  239. def testEarlyReturnOnNoFilter(self) -> None:
  240. """Tests that the absence of a test filter results in an early return."""
  241. with mock.patch.object(
  242. self._querier, '_GetQueryGeneratorForBuilder',
  243. return_value=None), mock.patch.object(
  244. self._querier, '_RunBigQueryCommandsForJsonOutput') as query_mock:
  245. results, expectation_files = self._querier.QueryBuilder(self._builder)
  246. query_mock.assert_not_called()
  247. self.assertEqual(results, [])
  248. self.assertEqual(expectation_files, None)
  249. def testRetryOnMemoryLimit(self) -> None:
  250. """Tests that queries are split and retried if the memory limit is hit."""
  251. def SideEffect(*_, **__) -> list:
  252. SideEffect.call_count += 1
  253. if SideEffect.call_count == 1:
  254. raise queries.MemoryLimitError()
  255. return []
  256. SideEffect.call_count = 0
  257. with mock.patch.object(
  258. self._querier,
  259. '_GetQueryGeneratorForBuilder',
  260. return_value=unittest_utils.SimpleSplitQueryGenerator(
  261. self._builder, ['filter_a', 'filter_b'], 10)), mock.patch.object(
  262. self._querier,
  263. '_RunBigQueryCommandsForJsonOutput') as query_mock:
  264. query_mock.side_effect = SideEffect
  265. self._querier.QueryBuilder(self._builder)
  266. self.assertEqual(query_mock.call_count, 2)
  267. args, _ = unittest_utils.GetArgsForMockCall(query_mock.call_args_list, 0)
  268. first_query = args[0][0]
  269. self.assertIn('filter_a', first_query)
  270. self.assertIn('filter_b', first_query)
  271. args, _ = unittest_utils.GetArgsForMockCall(query_mock.call_args_list, 1)
  272. second_query_first_half = args[0][0]
  273. self.assertIn('filter_a', second_query_first_half)
  274. self.assertNotIn('filter_b', second_query_first_half)
  275. second_query_second_half = args[0][1]
  276. self.assertIn('filter_b', second_query_second_half)
  277. self.assertNotIn('filter_a', second_query_second_half)
  278. class FillExpectationMapForBuildersUnittest(unittest.TestCase):
  279. def setUp(self) -> None:
  280. self._querier = unittest_utils.CreateGenericQuerier()
  281. self._query_patcher = mock.patch.object(self._querier, 'QueryBuilder')
  282. self._query_mock = self._query_patcher.start()
  283. self.addCleanup(self._query_patcher.stop)
  284. self._pool_patcher = mock.patch.object(multiprocessing_utils,
  285. 'GetProcessPool')
  286. self._pool_mock = self._pool_patcher.start()
  287. self._pool_mock.return_value = unittest_utils.FakePool()
  288. self.addCleanup(self._pool_patcher.stop)
  289. self._filter_patcher = mock.patch.object(self._querier,
  290. '_FilterOutInactiveBuilders')
  291. self._filter_mock = self._filter_patcher.start()
  292. self._filter_mock.side_effect = lambda b, _: b
  293. self.addCleanup(self._filter_patcher.stop)
  294. def testErrorOnMixedBuilders(self) -> None:
  295. """Tests that providing builders of mixed type is an error."""
  296. builders_to_fill = [
  297. data_types.BuilderEntry('ci_builder', constants.BuilderTypes.CI, False),
  298. data_types.BuilderEntry('try_builder', constants.BuilderTypes.TRY,
  299. False)
  300. ]
  301. with self.assertRaises(AssertionError):
  302. self._querier.FillExpectationMapForBuilders(
  303. data_types.TestExpectationMap({}), builders_to_fill)
  304. def testValidResults(self) -> None:
  305. """Tests functionality when valid results are returned by the query."""
  306. def SideEffect(builder: data_types.BuilderEntry,
  307. *args) -> Tuple[data_types.ResultListType, None]:
  308. del args
  309. if builder.name == 'matched_builder':
  310. return ([
  311. data_types.Result('foo', ['win'], 'Pass', 'step_name', 'build_id')
  312. ], None)
  313. if builder.name == 'matched_internal':
  314. return ([
  315. data_types.Result('foo', ['win'], 'Pass', 'step_name_internal',
  316. 'build_id')
  317. ], None)
  318. if builder.name == 'unmatched_internal':
  319. return ([
  320. data_types.Result('bar', [], 'Pass', 'step_name_internal',
  321. 'build_id')
  322. ], None)
  323. return ([data_types.Result('bar', [], 'Pass', 'step_name',
  324. 'build_id')], None)
  325. self._query_mock.side_effect = SideEffect
  326. expectation = data_types.Expectation('foo', ['win'], 'RetryOnFailure')
  327. expectation_map = data_types.TestExpectationMap({
  328. 'foo':
  329. data_types.ExpectationBuilderMap({
  330. expectation:
  331. data_types.BuilderStepMap(),
  332. }),
  333. })
  334. builders_to_fill = [
  335. data_types.BuilderEntry('matched_builder', constants.BuilderTypes.CI,
  336. False),
  337. data_types.BuilderEntry('unmatched_builder', constants.BuilderTypes.CI,
  338. False),
  339. data_types.BuilderEntry('matched_internal', constants.BuilderTypes.CI,
  340. True),
  341. data_types.BuilderEntry('unmatched_internal', constants.BuilderTypes.CI,
  342. True),
  343. ]
  344. unmatched_results = self._querier.FillExpectationMapForBuilders(
  345. expectation_map, builders_to_fill)
  346. stats = data_types.BuildStats()
  347. stats.AddPassedBuild()
  348. expected_expectation_map = {
  349. 'foo': {
  350. expectation: {
  351. 'chromium/ci:matched_builder': {
  352. 'step_name': stats,
  353. },
  354. 'chrome/ci:matched_internal': {
  355. 'step_name_internal': stats,
  356. },
  357. },
  358. },
  359. }
  360. self.assertEqual(expectation_map, expected_expectation_map)
  361. self.assertEqual(
  362. unmatched_results, {
  363. 'chromium/ci:unmatched_builder': [
  364. data_types.Result('bar', [], 'Pass', 'step_name', 'build_id'),
  365. ],
  366. 'chrome/ci:unmatched_internal': [
  367. data_types.Result('bar', [], 'Pass', 'step_name_internal',
  368. 'build_id'),
  369. ],
  370. })
  371. def testQueryFailureIsSurfaced(self) -> None:
  372. """Tests that a query failure is properly surfaced despite being async."""
  373. self._query_mock.side_effect = IndexError('failure')
  374. with self.assertRaises(IndexError):
  375. self._querier.FillExpectationMapForBuilders(
  376. data_types.TestExpectationMap(), [
  377. data_types.BuilderEntry('matched_builder',
  378. constants.BuilderTypes.CI, False)
  379. ])
  380. class FilterOutInactiveBuildersUnittest(unittest.TestCase):
  381. def setUp(self) -> None:
  382. self._subprocess_patcher = mock.patch(
  383. 'unexpected_passes_common.queries.subprocess.Popen')
  384. self._subprocess_mock = self._subprocess_patcher.start()
  385. self.addCleanup(self._subprocess_patcher.stop)
  386. self._querier = unittest_utils.CreateGenericQuerier()
  387. def testAllActiveBuilders(self) -> None:
  388. """Tests that no builders are removed if no inactive builders are found."""
  389. results = [{
  390. 'builder_name': 'foo_builder',
  391. }, {
  392. 'builder_name': 'bar_builder',
  393. }]
  394. fake_process = unittest_utils.FakeProcess(stdout=json.dumps(results))
  395. self._subprocess_mock.return_value = fake_process
  396. initial_builders = [
  397. data_types.BuilderEntry('foo_builder', constants.BuilderTypes.CI,
  398. False),
  399. data_types.BuilderEntry('bar_builder', constants.BuilderTypes.CI,
  400. False),
  401. ]
  402. expected_builders = copy.copy(initial_builders)
  403. filtered_builders = self._querier._FilterOutInactiveBuilders(
  404. initial_builders, constants.BuilderTypes.CI)
  405. self.assertEqual(filtered_builders, expected_builders)
  406. def testInactiveBuilders(self) -> None:
  407. """Tests that inactive builders are removed."""
  408. results = [{
  409. 'builder_name': 'foo_builder',
  410. }]
  411. fake_process = unittest_utils.FakeProcess(stdout=json.dumps(results))
  412. self._subprocess_mock.return_value = fake_process
  413. initial_builders = [
  414. data_types.BuilderEntry('foo_builder', constants.BuilderTypes.CI,
  415. False),
  416. data_types.BuilderEntry('bar_builder', constants.BuilderTypes.CI,
  417. False),
  418. ]
  419. expected_builders = [
  420. data_types.BuilderEntry('foo_builder', constants.BuilderTypes.CI, False)
  421. ]
  422. filtered_builders = self._querier._FilterOutInactiveBuilders(
  423. initial_builders, constants.BuilderTypes.CI)
  424. self.assertEqual(filtered_builders, expected_builders)
  425. def testByteConversion(self) -> None:
  426. """Tests that bytes are properly handled if returned."""
  427. results = [{
  428. 'builder_name': 'foo_builder',
  429. }]
  430. fake_process = unittest_utils.FakeProcess(stdout=json.dumps(results))
  431. self._subprocess_mock.return_value = fake_process
  432. initial_builders = [
  433. data_types.BuilderEntry('foo_builder', constants.BuilderTypes.CI,
  434. False),
  435. data_types.BuilderEntry('bar_builder', constants.BuilderTypes.CI,
  436. False),
  437. ]
  438. expected_builders = [
  439. data_types.BuilderEntry('foo_builder', constants.BuilderTypes.CI, False)
  440. ]
  441. filtered_builders = self._querier._FilterOutInactiveBuilders(
  442. initial_builders, constants.BuilderTypes.CI)
  443. self.assertEqual(filtered_builders, expected_builders)
  444. class RunBigQueryCommandsForJsonOutputUnittest(unittest.TestCase):
  445. def setUp(self) -> None:
  446. self._popen_patcher = mock.patch.object(subprocess, 'Popen')
  447. self._popen_mock = self._popen_patcher.start()
  448. self.addCleanup(self._popen_patcher.stop)
  449. self._querier = unittest_utils.CreateGenericQuerier()
  450. def testJsonReturned(self) -> None:
  451. """Tests that valid JSON parsed from stdout is returned."""
  452. query_output = [{'foo': 'bar'}]
  453. self._popen_mock.return_value = unittest_utils.FakeProcess(
  454. stdout=json.dumps(query_output))
  455. result = self._querier._RunBigQueryCommandsForJsonOutput([''], {})
  456. self.assertEqual(result, query_output)
  457. self._popen_mock.assert_called_once()
  458. def testJsonReturnedSplitQuery(self) -> None:
  459. """Tests that valid JSON is returned when a split query is used."""
  460. def SideEffect(*_, **__) -> unittest_utils.FakeProcess:
  461. SideEffect.call_count += 1
  462. if SideEffect.call_count == 1:
  463. return unittest_utils.FakeProcess(stdout=json.dumps([{'foo': 'bar'}]))
  464. return unittest_utils.FakeProcess(stdout=json.dumps([{'bar': 'baz'}]))
  465. SideEffect.call_count = 0
  466. self._popen_mock.side_effect = SideEffect
  467. result = self._querier._RunBigQueryCommandsForJsonOutput(['1', '2'], {})
  468. self.assertEqual(len(result), 2)
  469. self.assertIn({'foo': 'bar'}, result)
  470. self.assertIn({'bar': 'baz'}, result)
  471. def testExceptionRaisedOnFailure(self) -> None:
  472. """Tests that an exception is raised if the query fails."""
  473. self._popen_mock.return_value = unittest_utils.FakeProcess(returncode=1)
  474. with self.assertRaises(RuntimeError):
  475. self._querier._RunBigQueryCommandsForJsonOutput([''], {})
  476. def testRateLimitRetrySuccess(self) -> None:
  477. """Tests that rate limit errors result in retries."""
  478. def SideEffect(*_, **__) -> unittest_utils.FakeProcess:
  479. SideEffect.call_count += 1
  480. if SideEffect.call_count == 1:
  481. return unittest_utils.FakeProcess(
  482. returncode=1, stdout='Exceeded rate limits for foo.')
  483. return unittest_utils.FakeProcess(stdout='[]')
  484. SideEffect.call_count = 0
  485. self._popen_mock.side_effect = SideEffect
  486. self._querier._RunBigQueryCommandsForJsonOutput([''], {})
  487. self.assertEqual(self._popen_mock.call_count, 2)
  488. def testRateLimitRetryFailure(self) -> None:
  489. """Tests that rate limit errors stop retrying after enough iterations."""
  490. self._popen_mock.return_value = unittest_utils.FakeProcess(
  491. returncode=1, stdout='Exceeded rate limits for foo.')
  492. with self.assertRaises(RuntimeError):
  493. self._querier._RunBigQueryCommandsForJsonOutput([''], {})
  494. self.assertEqual(self._popen_mock.call_count, queries.MAX_QUERY_TRIES)
  495. class GenerateBigQueryCommandUnittest(unittest.TestCase):
  496. def testNoParametersSpecified(self) -> None:
  497. """Tests that no parameters are added if none are specified."""
  498. cmd = queries.GenerateBigQueryCommand('project', {})
  499. for element in cmd:
  500. self.assertFalse(element.startswith('--parameter'))
  501. def testParameterAddition(self) -> None:
  502. """Tests that specified parameters are added appropriately."""
  503. cmd = queries.GenerateBigQueryCommand('project', {
  504. '': {
  505. 'string': 'string_value'
  506. },
  507. 'INT64': {
  508. 'int': 1
  509. }
  510. })
  511. self.assertIn('--parameter=string::string_value', cmd)
  512. self.assertIn('--parameter=int:INT64:1', cmd)
  513. def testBatchMode(self) -> None:
  514. """Tests that batch mode adds the necessary arg."""
  515. cmd = queries.GenerateBigQueryCommand('project', {}, batch=True)
  516. self.assertIn('--batch', cmd)
  517. if __name__ == '__main__':
  518. unittest.main(verbosity=2)