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