Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
|
M-A Ruel
2013/08/18 00:18:19
will revert this change on next patchset.
| |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 import json | 6 import json |
| 7 import logging | 7 import logging |
| 8 import os | 8 import os |
| 9 import StringIO | 9 import StringIO |
| 10 import sys | 10 import sys |
| 11 import threading | 11 import threading |
| 12 import unittest | 12 import unittest |
| 13 import urllib2 | 13 import urllib2 |
| 14 | 14 |
| 15 import auto_stub | 15 import auto_stub |
| 16 | 16 |
| 17 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | 17 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| 18 sys.path.insert(0, ROOT_DIR) | 18 sys.path.insert(0, ROOT_DIR) |
| 19 | 19 |
| 20 import run_isolated | 20 import run_isolated |
| 21 import swarm_get_results | 21 import swarming |
| 22 | |
| 23 | |
| 24 FILE_NAME = u'test.isolated' | |
| 25 FILE_HASH = u'1' * 40 | |
|
M-A Ruel
2013/08/18 00:18:19
The code now enforces this looks like a sha1.
| |
| 26 TEST_NAME = u'unit_tests' | |
| 27 STDOUT_FOR_TRIGGER_LEN = 188 | |
| 22 | 28 |
| 23 | 29 |
| 24 TEST_CASE_SUCCESS = ( | 30 TEST_CASE_SUCCESS = ( |
| 25 '[----------] 2 tests from StaticCookiePolicyTest\n' | 31 '[----------] 2 tests from StaticCookiePolicyTest\n' |
| 26 '[ RUN ] StaticCookiePolicyTest.AllowAllCookiesTest\n' | 32 '[ RUN ] StaticCookiePolicyTest.AllowAllCookiesTest\n' |
| 27 '[ OK ] StaticCookiePolicyTest.AllowAllCookiesTest (0 ms)\n' | 33 '[ OK ] StaticCookiePolicyTest.AllowAllCookiesTest (0 ms)\n' |
| 28 '[ RUN ] StaticCookiePolicyTest.BlockAllCookiesTest\n' | 34 '[ RUN ] StaticCookiePolicyTest.BlockAllCookiesTest\n' |
| 29 '[ OK ] StaticCookiePolicyTest.BlockAllCookiesTest (0 ms)\n' | 35 '[ OK ] StaticCookiePolicyTest.BlockAllCookiesTest (0 ms)\n' |
| 30 '[----------] 2 tests from StaticCookiePolicyTest (0 ms total)\n' | 36 '[----------] 2 tests from StaticCookiePolicyTest (0 ms total)\n' |
| 31 '\n' | 37 '\n' |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 126 url_response.code = 200 | 132 url_response.code = 200 |
| 127 url_response.msg = 'OK' | 133 url_response.msg = 'OK' |
| 128 return url_response | 134 return url_response |
| 129 | 135 |
| 130 | 136 |
| 131 def get_swarm_results(keys): | 137 def get_swarm_results(keys): |
| 132 """Simplifies the call to yield_results(). | 138 """Simplifies the call to yield_results(). |
| 133 | 139 |
| 134 The timeout is hard-coded to 10 seconds. | 140 The timeout is hard-coded to 10 seconds. |
| 135 """ | 141 """ |
| 136 return list( | 142 return list(swarming.yield_results('http://host:9001', keys, 10., None)) |
| 137 swarm_get_results.yield_results( | |
| 138 'http://host:9001', keys, 10., None)) | |
| 139 | 143 |
| 140 | 144 |
| 141 class TestCase(auto_stub.TestCase): | 145 class TestCase(auto_stub.TestCase): |
| 142 """Base class that defines the url_open mock.""" | 146 """Base class that defines the url_open mock.""" |
| 143 def setUp(self): | 147 def setUp(self): |
| 144 super(TestCase, self).setUp() | 148 super(TestCase, self).setUp() |
| 145 self._lock = threading.Lock() | 149 self._lock = threading.Lock() |
| 146 self.requests = [] | 150 self.requests = [] |
| 147 self.mock( | 151 self.mock(swarming.run_isolated, 'url_open', self._url_open) |
| 148 swarm_get_results.run_isolated, 'url_open', | |
| 149 self._url_open) | |
| 150 | 152 |
| 151 def tearDown(self): | 153 def tearDown(self): |
| 152 try: | 154 try: |
| 153 if not self.has_failed(): | 155 if not self.has_failed(): |
| 154 self.assertEqual([], self.requests) | 156 self.assertEqual([], self.requests) |
| 155 finally: | 157 finally: |
| 156 super(TestCase, self).tearDown() | 158 super(TestCase, self).tearDown() |
| 157 | 159 |
| 158 def _url_open(self, url, **kwargs): | 160 def _url_open(self, url, **kwargs): |
| 159 logging.info('url_open(%s)', url) | 161 logging.info('url_open(%s)', url) |
| 160 with self._lock: | 162 with self._lock: |
| 161 # Since the client is multi-threaded, requests can be processed out of | 163 # Since the client is multi-threaded, requests can be processed out of |
| 162 # order. | 164 # order. |
| 163 for index, r in enumerate(self.requests): | 165 for index, r in enumerate(self.requests): |
| 164 if r[0] == url and r[1] == kwargs: | 166 if r[0] == url and r[1] == kwargs: |
| 165 _, _, returned = self.requests.pop(index) | 167 _, _, returned = self.requests.pop(index) |
| 166 break | 168 break |
| 167 else: | 169 else: |
| 168 self.fail('Failed to find url %s' % url) | 170 self.fail('Failed to find url %s' % url) |
| 169 return returned | 171 return returned |
| 170 | 172 |
| 171 | 173 |
| 172 class TestGetTestKeys(TestCase): | 174 class TestGetTestKeys(TestCase): |
| 173 def test_no_keys(self): | 175 def test_no_keys(self): |
| 174 self.mock(swarm_get_results.time, 'sleep', lambda x: x) | 176 self.mock(swarming.time, 'sleep', lambda x: x) |
| 175 self.requests = [ | 177 self.requests = [ |
| 176 ( | 178 ( |
| 177 'http://host:9001/get_matching_test_cases?name=my_test', | 179 'http://host:9001/get_matching_test_cases?name=my_test', |
| 178 {'retry_404': True}, | 180 {'retry_404': True}, |
| 179 StringIO.StringIO('No matching Test Cases'), | 181 StringIO.StringIO('No matching Test Cases'), |
| 180 ) for _ in range(run_isolated.URL_OPEN_MAX_ATTEMPTS) | 182 ) for _ in range(run_isolated.URL_OPEN_MAX_ATTEMPTS) |
| 181 ] | 183 ] |
| 182 try: | 184 try: |
| 183 swarm_get_results.get_test_keys('http://host:9001', 'my_test') | 185 swarming.get_test_keys('http://host:9001', 'my_test') |
| 184 self.fail() | 186 self.fail() |
| 185 except swarm_get_results.Failure as e: | 187 except swarming.Failure as e: |
| 186 msg = ( | 188 msg = ( |
| 187 'Error: Unable to find any tests with the name, my_test, on swarm ' | 189 'Error: Unable to find any tests with the name, my_test, on swarm ' |
| 188 'server') | 190 'server') |
| 189 self.assertEqual(msg, e.args[0]) | 191 self.assertEqual(msg, e.args[0]) |
| 190 | 192 |
| 191 def test_no_keys_on_first_attempt(self): | 193 def test_no_keys_on_first_attempt(self): |
| 192 self.mock(swarm_get_results.time, 'sleep', lambda x: x) | 194 self.mock(swarming.time, 'sleep', lambda x: x) |
| 193 keys = ['key_1', 'key_2'] | 195 keys = ['key_1', 'key_2'] |
| 194 self.requests = [ | 196 self.requests = [ |
| 195 ( | 197 ( |
| 196 'http://host:9001/get_matching_test_cases?name=my_test', | 198 'http://host:9001/get_matching_test_cases?name=my_test', |
| 197 {'retry_404': True}, | 199 {'retry_404': True}, |
| 198 StringIO.StringIO('No matching Test Cases'), | 200 StringIO.StringIO('No matching Test Cases'), |
| 199 ), | 201 ), |
| 200 ( | 202 ( |
| 201 'http://host:9001/get_matching_test_cases?name=my_test', | 203 'http://host:9001/get_matching_test_cases?name=my_test', |
| 202 {'retry_404': True}, | 204 {'retry_404': True}, |
| 203 StringIO.StringIO(json.dumps(keys)), | 205 StringIO.StringIO(json.dumps(keys)), |
| 204 ), | 206 ), |
| 205 ] | 207 ] |
| 206 actual = swarm_get_results.get_test_keys('http://host:9001', 'my_test') | 208 actual = swarming.get_test_keys('http://host:9001', 'my_test') |
| 207 self.assertEqual(keys, actual) | 209 self.assertEqual(keys, actual) |
| 208 | 210 |
| 209 def test_find_keys(self): | 211 def test_find_keys(self): |
| 210 keys = ['key_1', 'key_2'] | 212 keys = ['key_1', 'key_2'] |
| 211 self.requests = [ | 213 self.requests = [ |
| 212 ( | 214 ( |
| 213 'http://host:9001/get_matching_test_cases?name=my_test', | 215 'http://host:9001/get_matching_test_cases?name=my_test', |
| 214 {'retry_404': True}, | 216 {'retry_404': True}, |
| 215 StringIO.StringIO(json.dumps(keys)), | 217 StringIO.StringIO(json.dumps(keys)), |
| 216 ), | 218 ), |
| 217 ] | 219 ] |
| 218 actual = swarm_get_results.get_test_keys('http://host:9001', 'my_test') | 220 actual = swarming.get_test_keys('http://host:9001', 'my_test') |
| 219 self.assertEqual(keys, actual) | 221 self.assertEqual(keys, actual) |
| 220 | 222 |
| 221 | 223 |
| 222 class TestGetSwarmResults(TestCase): | 224 class TestGetSwarmResults(TestCase): |
| 223 def test_success(self): | 225 def test_success(self): |
| 224 self.requests = [ | 226 self.requests = [ |
| 225 ( | 227 ( |
| 226 'http://host:9001/get_result?r=key1', | 228 'http://host:9001/get_result?r=key1', |
| 227 {'retry_404': False, 'retry_50x': False}, | 229 {'retry_404': False, 'retry_50x': False}, |
| 228 generate_url_response(0, SWARM_OUTPUT_SUCCESS, '0, 0'), | 230 generate_url_response(0, SWARM_OUTPUT_SUCCESS, '0, 0'), |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 263 def test_url_errors(self): | 265 def test_url_errors(self): |
| 264 # NOTE: get_swarm_results() hardcodes timeout=10. range(12) is because of an | 266 # NOTE: get_swarm_results() hardcodes timeout=10. range(12) is because of an |
| 265 # additional time.time() call deep in run_isolated.url_open(). | 267 # additional time.time() call deep in run_isolated.url_open(). |
| 266 now = {} | 268 now = {} |
| 267 lock = threading.Lock() | 269 lock = threading.Lock() |
| 268 def get_now(): | 270 def get_now(): |
| 269 t = threading.current_thread() | 271 t = threading.current_thread() |
| 270 with lock: | 272 with lock: |
| 271 return now.setdefault(t, range(12)).pop(0) | 273 return now.setdefault(t, range(12)).pop(0) |
| 272 self.mock( | 274 self.mock( |
| 273 swarm_get_results.run_isolated.HttpService, | 275 swarming.run_isolated.HttpService, |
| 274 'sleep_before_retry', | 276 'sleep_before_retry', |
| 275 staticmethod(lambda _x, _y: None)) | 277 staticmethod(lambda _x, _y: None)) |
| 276 self.mock(swarm_get_results, 'now', get_now) | 278 self.mock(swarming, 'now', get_now) |
| 277 # The actual number of requests here depends on 'now' progressing to 10 | 279 # The actual number of requests here depends on 'now' progressing to 10 |
| 278 # seconds. It's called twice per loop. | 280 # seconds. It's called twice per loop. |
| 279 self.requests = [ | 281 self.requests = [ |
| 280 ( | 282 ( |
| 281 'http://host:9001/get_result?r=key1', | 283 'http://host:9001/get_result?r=key1', |
| 282 {'retry_404': False, 'retry_50x': False}, | 284 {'retry_404': False, 'retry_50x': False}, |
| 283 None, | 285 None, |
| 284 ), | 286 ), |
| 285 ( | 287 ( |
| 286 'http://host:9001/get_result?r=key1', | 288 'http://host:9001/get_result?r=key1', |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 350 ] | 352 ] |
| 351 expected = [ | 353 expected = [ |
| 352 gen_yielded_data(0, TEST_SHARD_OUTPUT_1, '0, 0'), | 354 gen_yielded_data(0, TEST_SHARD_OUTPUT_1, '0, 0'), |
| 353 gen_yielded_data(1, TEST_SHARD_OUTPUT_2, '0, 0'), | 355 gen_yielded_data(1, TEST_SHARD_OUTPUT_2, '0, 0'), |
| 354 gen_yielded_data(2, TEST_SHARD_OUTPUT_3, '0, 0'), | 356 gen_yielded_data(2, TEST_SHARD_OUTPUT_3, '0, 0'), |
| 355 ] | 357 ] |
| 356 actual = get_swarm_results(['key1', 'key1-repeat', 'key2', 'key3']) | 358 actual = get_swarm_results(['key1', 'key1-repeat', 'key2', 'key3']) |
| 357 self.assertEqual(expected, sorted(actual)) | 359 self.assertEqual(expected, sorted(actual)) |
| 358 | 360 |
| 359 | 361 |
| 362 def chromium_tasks(retrieval_url): | |
| 363 return [ | |
| 364 { | |
| 365 u'action': [ | |
| 366 u'python', u'run_isolated.py', | |
| 367 u'--hash', FILE_HASH, | |
| 368 u'--remote', retrieval_url + u'default-gzip/', | |
| 369 ], | |
| 370 u'decorate_output': False, | |
| 371 u'test_name': u'Run Test', | |
| 372 u'time_out': 600, | |
| 373 }, | |
| 374 { | |
| 375 u'action' : [ | |
| 376 u'python', u'swarm_cleanup.py', | |
| 377 ], | |
| 378 u'decorate_output': False, | |
| 379 u'test_name': u'Clean Up', | |
| 380 u'time_out': 600, | |
| 381 } | |
| 382 ] | |
| 383 | |
| 384 | |
| 385 def generate_expected_json( | |
| 386 shards, | |
| 387 os_image, | |
| 388 working_dir, | |
| 389 cac, | |
| 390 profile): | |
| 391 retrieval_url = cac + '/content/retrieve/' | |
| 392 os_value = unicode(swarming.PLATFORM_MAPPING[os_image]) | |
| 393 expected = { | |
| 394 u'cleanup': u'root', | |
| 395 u'configurations': [ | |
| 396 { | |
| 397 u'config_name': os_value, | |
| 398 u'dimensions': { | |
| 399 u'os': os_value, | |
| 400 }, | |
| 401 u'min_instances': shards, | |
| 402 }, | |
| 403 ], | |
| 404 u'data': [[retrieval_url + u'default/', u'swarm_data.zip']], | |
| 405 u'env_vars': {}, | |
| 406 u'restart_on_failure': True, | |
| 407 u'test_case_name': TEST_NAME, | |
| 408 u'tests': chromium_tasks(retrieval_url), | |
| 409 u'working_dir': unicode(working_dir), | |
| 410 u'priority': 101, | |
| 411 } | |
| 412 if shards > 1: | |
| 413 expected[u'env_vars'][u'GTEST_SHARD_INDEX'] = u'%(instance_index)s' | |
| 414 expected[u'env_vars'][u'GTEST_TOTAL_SHARDS'] = u'%(num_instances)s' | |
| 415 if profile: | |
| 416 expected[u'tests'][0][u'action'].append(u'--verbose') | |
| 417 return expected | |
| 418 | |
| 419 | |
| 420 class MockZipFile(object): | |
| 421 def __init__(self, filename, mode): | |
| 422 pass | |
| 423 | |
| 424 def write(self, source, dest=None): | |
| 425 pass | |
| 426 | |
| 427 def close(self): | |
| 428 pass | |
| 429 | |
| 430 | |
| 431 def MockUrlOpen(url, _data, has_return_value): | |
| 432 if '/content/contains' in url: | |
| 433 return StringIO.StringIO(has_return_value) | |
| 434 return StringIO.StringIO('{}') | |
| 435 | |
| 436 | |
| 437 def MockUrlOpenHasZip(url, data=None, content_type=None): | |
| 438 assert content_type in (None, 'application/json', 'application/octet-stream') | |
| 439 return MockUrlOpen(url, data, has_return_value=chr(1)) | |
| 440 | |
| 441 | |
| 442 def MockUrlOpenNoZip(url, data=None, content_type=None): | |
| 443 assert content_type in (None, 'application/json', 'application/octet-stream') | |
| 444 return MockUrlOpen(url, data, has_return_value=chr(0)) | |
| 445 | |
| 446 | |
| 447 class ManifestTest(auto_stub.TestCase): | |
| 448 def setUp(self): | |
| 449 self.mock(swarming.time, 'sleep', lambda x: None) | |
| 450 self.mock(swarming.zipfile, 'ZipFile', MockZipFile) | |
| 451 self.mock(sys, 'stdout', StringIO.StringIO()) | |
| 452 self.mock(sys, 'stderr', StringIO.StringIO()) | |
| 453 | |
| 454 def tearDown(self): | |
| 455 if not self.has_failed(): | |
| 456 self._check_output('', '') | |
| 457 super(ManifestTest, self).tearDown() | |
| 458 | |
| 459 def _check_output(self, out, err): | |
| 460 self.assertEqual(out, sys.stdout.getvalue()) | |
| 461 self.assertEqual(err, sys.stderr.getvalue()) | |
| 462 | |
| 463 # Flush their content by mocking them again. | |
| 464 self.mock(sys, 'stdout', StringIO.StringIO()) | |
| 465 self.mock(sys, 'stderr', StringIO.StringIO()) | |
| 466 | |
| 467 def test_basic_manifest(self): | |
| 468 manifest = swarming.Manifest( | |
| 469 manifest_hash=FILE_HASH, | |
| 470 test_name=TEST_NAME, | |
| 471 shards=2, | |
| 472 test_filter='*', | |
| 473 os_image='win32', | |
| 474 working_dir='swarm_tests', | |
| 475 cac='http://localhost:8081', | |
| 476 verbose=False, | |
| 477 profile=False, | |
| 478 priority=101) | |
| 479 | |
| 480 swarming.chromium_setup(manifest) | |
| 481 manifest_json = json.loads(manifest.to_json()) | |
| 482 | |
| 483 expected = generate_expected_json( | |
| 484 shards=2, | |
| 485 os_image='win32', | |
| 486 working_dir='swarm_tests', | |
| 487 cac='http://localhost:8081', | |
| 488 profile=False) | |
| 489 self.assertEqual(expected, manifest_json) | |
| 490 | |
| 491 def test_basic_linux(self): | |
| 492 """A basic linux manifest test to ensure that windows specific values | |
| 493 aren't used. | |
| 494 """ | |
| 495 manifest = swarming.Manifest( | |
| 496 manifest_hash=FILE_HASH, | |
| 497 test_name=TEST_NAME, | |
| 498 shards=1, | |
| 499 test_filter='*', | |
| 500 os_image='linux2', | |
| 501 working_dir='swarm_tests', | |
| 502 cac='http://localhost:8081', | |
| 503 verbose=False, | |
| 504 profile=False, | |
| 505 priority=101) | |
| 506 | |
| 507 swarming.chromium_setup(manifest) | |
| 508 manifest_json = json.loads(manifest.to_json()) | |
| 509 | |
| 510 expected = generate_expected_json( | |
| 511 shards=1, | |
| 512 os_image='linux2', | |
| 513 working_dir='swarm_tests', | |
| 514 cac='http://localhost:8081', | |
| 515 profile=False) | |
| 516 self.assertEqual(expected, manifest_json) | |
| 517 | |
| 518 def test_basic_linux_profile(self): | |
| 519 manifest = swarming.Manifest( | |
| 520 manifest_hash=FILE_HASH, | |
| 521 test_name=TEST_NAME, | |
| 522 shards=1, | |
| 523 test_filter='*', | |
| 524 os_image='linux2', | |
| 525 working_dir='swarm_tests', | |
| 526 cac='http://localhost:8081', | |
| 527 verbose=False, | |
| 528 profile=True, | |
| 529 priority=101) | |
| 530 | |
| 531 swarming.chromium_setup(manifest) | |
| 532 manifest_json = json.loads(manifest.to_json()) | |
| 533 | |
| 534 expected = generate_expected_json( | |
| 535 shards=1, | |
| 536 os_image='linux2', | |
| 537 working_dir='swarm_tests', | |
| 538 cac='http://localhost:8081', | |
| 539 profile=True) | |
| 540 self.assertEqual(expected, manifest_json) | |
| 541 | |
| 542 def test_process_manifest_success(self): | |
| 543 self.mock(swarming.run_isolated, 'url_open', MockUrlOpenNoZip) | |
| 544 | |
| 545 result = swarming.process_manifest( | |
| 546 file_sha1_or_isolated=FILE_HASH, | |
| 547 test_name=TEST_NAME, | |
| 548 shards=1, | |
| 549 test_filter='*', | |
| 550 os_image='linux2', | |
| 551 working_dir='swarm_tests', | |
| 552 cac='http://localhost:8081', | |
| 553 swarm_url='http://localhost:8082', | |
| 554 verbose=False, | |
| 555 profile=False, | |
| 556 priority=101) | |
| 557 self.assertEqual(0, result) | |
| 558 | |
| 559 # Just assert it printed enough, since it contains variable output. | |
| 560 out = sys.stdout.getvalue() | |
| 561 self.assertTrue( | |
| 562 len(out) > STDOUT_FOR_TRIGGER_LEN, | |
| 563 (out, sys.stderr.getvalue())) | |
| 564 self.assertTrue('Zip file not on server, starting uploading.' in out) | |
| 565 self.mock(sys, 'stdout', StringIO.StringIO()) | |
| 566 | |
| 567 def test_process_manifest_success_zip_already_uploaded(self): | |
| 568 self.mock(swarming.run_isolated, 'url_open', MockUrlOpenHasZip) | |
| 569 | |
| 570 result = swarming.process_manifest( | |
| 571 file_sha1_or_isolated=FILE_HASH, | |
| 572 test_name=TEST_NAME, | |
| 573 shards=1, | |
| 574 test_filter='*', | |
| 575 os_image='linux2', | |
| 576 working_dir='swarm_tests', | |
| 577 cac='http://localhost:8081', | |
| 578 swarm_url='http://localhost:8082', | |
| 579 verbose=False, | |
| 580 profile=False, | |
| 581 priority=101) | |
| 582 self.assertEqual(0, result) | |
| 583 | |
| 584 # Just assert it printed enough, since it contains variable output. | |
| 585 out = sys.stdout.getvalue() | |
| 586 self.assertTrue(len(out) > STDOUT_FOR_TRIGGER_LEN) | |
| 587 self.assertTrue('Zip file already on server, no need to reupload.' in out) | |
| 588 self.mock(sys, 'stdout', StringIO.StringIO()) | |
| 589 | |
| 590 def test_no_request(self): | |
| 591 try: | |
| 592 swarming.main(['trigger']) | |
| 593 self.fail() | |
| 594 except SystemExit as e: | |
| 595 self.assertEqual(2, e.code) | |
| 596 self._check_output( | |
| 597 '', | |
| 598 'Usage: swarming.py trigger [options]\n\n' | |
| 599 'swarming.py: error: At least one --task is required.\n') | |
| 600 | |
| 601 | |
| 360 if __name__ == '__main__': | 602 if __name__ == '__main__': |
| 361 logging.basicConfig( | 603 logging.basicConfig( |
| 362 level=logging.DEBUG if '-v' in sys.argv else logging.ERROR) | 604 level=logging.DEBUG if '-v' in sys.argv else logging.ERROR) |
| 363 if '-v' in sys.argv: | 605 if '-v' in sys.argv: |
| 364 unittest.TestCase.maxDiff = None | 606 unittest.TestCase.maxDiff = None |
| 365 unittest.main() | 607 unittest.main() |
| OLD | NEW |