Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(260)

Side by Side Diff: appengine/swarming/local_smoke_test.py

Issue 1373133004: Fixes and add smoke test: hard timeout on isolated task. (Closed) Base URL: git@github.com:luci/luci-py.git@master
Patch Set: . Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2014 The Swarming Authors. All rights reserved. 2 # Copyright 2014 The Swarming Authors. All rights reserved.
3 # Use of this source code is governed by the Apache v2.0 license that can be 3 # Use of this source code is governed by the Apache v2.0 license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Integration test for the Swarming server, Swarming bot and Swarming client. 6 """Integration test for the Swarming server, Swarming bot and Swarming client.
7 7
8 It starts both a Swarming server and a Swarming bot and triggers tasks with the 8 It starts both a Swarming server and a Swarming bot and triggers tasks with the
9 Swarming client to ensure the system works end to end. 9 Swarming client to ensure the system works end to end.
10 """ 10 """
(...skipping 25 matching lines...) Expand all
36 import test_env_bot 36 import test_env_bot
37 test_env_bot.init_symlinks(BOT_DIR) 37 test_env_bot.init_symlinks(BOT_DIR)
38 38
39 from api import os_utilities 39 from api import os_utilities
40 40
41 41
42 # Signal as seen by the process. 42 # Signal as seen by the process.
43 SIGNAL_TERM = -1073741510 if sys.platform == 'win32' else -signal.SIGTERM 43 SIGNAL_TERM = -1073741510 if sys.platform == 'win32' else -signal.SIGTERM
44 44
45 45
46 # For the isolated tests that outputs a file named result.txt containing 'hey'.
47 ISOLATE_HELLO_WORLD = {
48 'variables': {
49 'command': ['python', '-u', 'hello_world.py'],
50 'files': ['hello_world.py'],
51 },
52 }
53
54 RESULT_HEY_ISOLATED_OUT = {
55 u'isolated': u'f10f4c42b38ca01726610f9575ba695468c32108',
56 u'isolatedserver': u'http://localhost:10050',
57 u'namespace': u'default-gzip',
58 u'view_url':
59 u'http://localhost:10050/browse?namespace=default-gzip'
60 '&hash=f10f4c42b38ca01726610f9575ba695468c32108',
61 }
62
63 RESULT_HEY_OUTPUTS_REF = {
64 u'isolated': u'f10f4c42b38ca01726610f9575ba695468c32108',
65 u'isolatedserver': u'http://localhost:10050',
66 u'namespace': u'default-gzip',
67 u'view_url':
68 u'http://localhost:10050/browse?namespace=default-gzip'
69 '&hash=f10f4c42b38ca01726610f9575ba695468c32108',
70 }
71
72
46 class SwarmingClient(object): 73 class SwarmingClient(object):
47 def __init__(self, swarming_server, isolate_server): 74 def __init__(self, swarming_server, isolate_server):
48 self._swarming_server = swarming_server 75 self._swarming_server = swarming_server
49 self._isolate_server = isolate_server 76 self._isolate_server = isolate_server
50 self._tmpdir = tempfile.mkdtemp(prefix='swarming_client') 77 self._tmpdir = tempfile.mkdtemp(prefix='swarming_client')
51 self._index = 0 78 self._index = 0
52 79
53 def isolate(self, isolate_path, isolated_path): 80 def isolate(self, isolate_path, isolated_path):
54 cmd = [ 81 cmd = [
55 sys.executable, 'isolate.py', 'archive', 82 sys.executable, 'isolate.py', 'archive',
(...skipping 274 matching lines...) Expand 10 before | Expand all | Expand 10 after
330 # account. 357 # account.
331 res = self.servers.http_client.request( 358 res = self.servers.http_client.request(
332 '/restricted/upload/bot_config', 359 '/restricted/upload/bot_config',
333 body=urllib.urlencode({'script': bot_config_content})) 360 body=urllib.urlencode({'script': bot_config_content}))
334 self.assertEqual(200, res.http_code, res.body) 361 self.assertEqual(200, res.http_code, res.body)
335 bot_version2 = self.assertOneTask(args, summary, {}) 362 bot_version2 = self.assertOneTask(args, summary, {})
336 self.assertNotEqual(bot_version1, bot_version2) 363 self.assertNotEqual(bot_version1, bot_version2)
337 364
338 def test_isolated(self): 365 def test_isolated(self):
339 # Make an isolated file, archive it. 366 # Make an isolated file, archive it.
340 isolate = {
341 'variables': {
342 'command': ['python', 'hello_world.py'],
343 'files': ['hello_world.py'],
344 },
345 }
346 hello_world = '\n'.join(( 367 hello_world = '\n'.join((
347 'import os', 368 'import os',
348 'import sys', 369 'import sys',
349 'print(\'hi\')', 370 'print(\'hi\')',
350 'with open(os.path.join(sys.argv[1], \'result.txt\'), \'wb\') as f:', 371 'with open(os.path.join(sys.argv[1], \'result.txt\'), \'wb\') as f:',
351 ' f.write(\'hey\')')) 372 ' f.write(\'hey\')'))
352 expected_summary = self.gen_expected( 373 expected_summary = self.gen_expected(
353 name=u'yo', 374 name=u'isolated_task',
354 isolated_out={ 375 isolated_out=RESULT_HEY_ISOLATED_OUT,
355 u'isolated': u'f10f4c42b38ca01726610f9575ba695468c32108', 376 outputs=[u'hi\n'],
356 u'isolatedserver': u'http://localhost:10050', 377 outputs_ref=RESULT_HEY_OUTPUTS_REF)
357 u'namespace': u'default-gzip',
358 u'view_url':
359 u'http://localhost:10050/browse?namespace=default-gzip'
360 '&hash=f10f4c42b38ca01726610f9575ba695468c32108',
361 },
362 outputs=[
363 u'hi\n'
364 ],
365 outputs_ref={
366 u'isolated': u'f10f4c42b38ca01726610f9575ba695468c32108',
367 u'isolatedserver': u'http://localhost:10050',
368 u'namespace': u'default-gzip',
369 u'view_url':
370 u'http://localhost:10050/browse?namespace=default-gzip'
371 '&hash=f10f4c42b38ca01726610f9575ba695468c32108',
372 })
373 expected_files = {os.path.join('0', 'result.txt'): 'hey'} 378 expected_files = {os.path.join('0', 'result.txt'): 'hey'}
379 self._run_isolated(
380 hello_world, 'isolated_task', ['--', '${ISOLATED_OUTDIR}'],
381 expected_summary, expected_files)
382
383 def test_isolated_hard_timeout(self):
384 # Make an isolated file, archive it, have it time out. Similar to
385 # test_hard_timeout. The script doesn't handle signal so it failed the grace
386 # period.
387 hello_world = '\n'.join((
388 'import os',
389 'import sys',
390 'import time',
391 'sys.stdout.write(\'hi\\n\')',
392 'sys.stdout.flush()',
393 'time.sleep(120)',
394 'with open(os.path.join(sys.argv[1], \'result.txt\'), \'wb\') as f:',
395 ' f.write(\'hey\')'))
396 expected_summary = self.gen_expected(
397 name=u'isolated_hard_timeout',
398 exit_codes=[SIGNAL_TERM],
399 failure=True,
400 state=0x40) # task_result.State.TIMED_OUT
401 self._run_isolated(
402 hello_world, 'isolated_hard_timeout',
403 ['--hard-timeout', '1', '--', '${ISOLATED_OUTDIR}'],
404 expected_summary, {})
405
406 def test_isolated_hard_timeout_grace(self):
407 # Make an isolated file, archive it, have it time out. Similar to
408 # test_hard_timeout. The script handles signal so it send results back.
409 hello_world = '\n'.join((
410 'import os',
411 'import signal',
412 'import sys',
413 'import time',
414 'l = []',
415 'def handler(signum, _):',
416 ' l.append(signum)',
417 ' sys.stdout.write(\'got signal %d\\n\' % signum)',
418 ' sys.stdout.flush()',
419 'signal.signal(signal.%s, handler)' %
420 ('SIGBREAK' if sys.platform == 'win32' else 'SIGTERM'),
421 'sys.stdout.write(\'hi\\n\')',
422 'sys.stdout.flush()',
423 'while not l:',
424 ' try:',
425 ' time.sleep(0.01)',
426 ' except IOError:',
427 ' print(\'ioerror\')',
428 'with open(os.path.join(sys.argv[1], \'result.txt\'), \'wb\') as f:',
429 ' f.write(\'hey\')'))
430 expected_summary = self.gen_expected(
431 name=u'isolated_hard_timeout_grace',
432 isolated_out=RESULT_HEY_ISOLATED_OUT,
433 outputs=[u'hi\ngot signal 15\n'],
434 outputs_ref=RESULT_HEY_OUTPUTS_REF,
435 failure=True,
436 state=0x40) # task_result.State.TIMED_OUT
437 expected_files = {os.path.join('0', 'result.txt'): 'hey'}
438 # Sadly we have to use a slow timeout here as this test can be flaky; it
439 # will not result in the expected result if run_isolated receives the signal
440 # before it had time to download the files, map them and start the child
441 # process, which then had time to setup its handler.
442 # TODO(maruel): When using run_isolated, have run_isolated enforces the hard
443 # timeout, while I/O timeout is still enforced by task_runner. This is due
444 # to run_isolated not piping stdout so it doesn't know about stdout/stderr
445 # output. Once this is fixed, the timeout can be reduced back to 1s.
446 self._run_isolated(
447 hello_world, 'isolated_hard_timeout_grace',
448 ['--hard-timeout', '3', '--', '${ISOLATED_OUTDIR}'],
449 expected_summary, expected_files)
450
451 def _run_isolated(self, hello_world, name, args, expected_summary,
452 expected_files):
453 # Shared code for all test_isolated_* test cases.
374 tmpdir = tempfile.mkdtemp(prefix='swarming_smoke') 454 tmpdir = tempfile.mkdtemp(prefix='swarming_smoke')
375 try: 455 try:
376 isolate_path = os.path.join(tmpdir, 'i.isolate') 456 isolate_path = os.path.join(tmpdir, 'i.isolate')
377 isolated_path = os.path.join(tmpdir, 'i.isolated') 457 isolated_path = os.path.join(tmpdir, 'i.isolated')
378 with open(isolate_path, 'wb') as f: 458 with open(isolate_path, 'wb') as f:
379 json.dump(isolate, f) 459 json.dump(ISOLATE_HELLO_WORLD, f)
380 with open(os.path.join(tmpdir, 'hello_world.py'), 'wb') as f: 460 with open(os.path.join(tmpdir, 'hello_world.py'), 'wb') as f:
381 f.write(hello_world) 461 f.write(hello_world)
382 isolated_hash = self.client.isolate(isolate_path, isolated_path) 462 isolated_hash = self.client.isolate(isolate_path, isolated_path)
383 task_id = self.client.task_trigger_isolated( 463 task_id = self.client.task_trigger_isolated(
384 'yo', isolated_hash, extra=['--', '${ISOLATED_OUTDIR}']) 464 name, isolated_hash, extra=args)
385 actual_summary, actual_files = self.client.task_collect(task_id) 465 actual_summary, actual_files = self.client.task_collect(task_id)
386 self.assertResults(expected_summary, actual_summary) 466 self.assertResults(expected_summary, actual_summary)
387 actual_files.pop('summary.json') 467 actual_files.pop('summary.json')
388 self.assertEqual(expected_files, actual_files) 468 self.assertEqual(expected_files, actual_files)
389 finally: 469 finally:
390 shutil.rmtree(tmpdir) 470 shutil.rmtree(tmpdir)
391 471
392 def assertResults(self, expected, result): 472 def assertResults(self, expected, result):
393 self.assertEqual(['shards'], result.keys()) 473 self.assertEqual(['shards'], result.keys())
394 self.assertEqual(1, len(result['shards'])) 474 self.assertEqual(1, len(result['shards']))
(...skipping 15 matching lines...) Expand all
410 def assertOneTask(self, args, expected_summary, expected_files): 490 def assertOneTask(self, args, expected_summary, expected_files):
411 """Runs a single task at a time.""" 491 """Runs a single task at a time."""
412 task_id = self.client.task_trigger_raw(args) 492 task_id = self.client.task_trigger_raw(args)
413 actual_summary, actual_files = self.client.task_collect(task_id) 493 actual_summary, actual_files = self.client.task_collect(task_id)
414 bot_version = self.assertResults(expected_summary, actual_summary) 494 bot_version = self.assertResults(expected_summary, actual_summary)
415 actual_files.pop('summary.json') 495 actual_files.pop('summary.json')
416 self.assertEqual(expected_files, actual_files) 496 self.assertEqual(expected_files, actual_files)
417 return bot_version 497 return bot_version
418 498
419 499
420 def cleanup(bot, client, servers, print_all): 500 def cleanup(bot, client, servers, print_all, leak):
421 """Kills bot, kills server, print logs if failed, delete tmpdir.""" 501 """Kills bot, kills server, print logs if failed, delete tmpdir."""
422 try: 502 try:
423 try: 503 try:
424 try: 504 try:
425 if bot: 505 if bot:
426 bot.stop() 506 bot.stop(leak)
427 finally: 507 finally:
428 if servers: 508 if servers:
429 servers.stop() 509 servers.stop(leak)
430 finally: 510 finally:
431 if print_all: 511 if print_all:
432 if bot: 512 if bot:
433 bot.dump_log() 513 bot.dump_log()
434 if servers: 514 if servers:
435 servers.dump_log() 515 servers.dump_log()
436 if client: 516 if client:
437 client.dump_log() 517 client.dump_log()
438 finally: 518 finally:
439 if client: 519 if client and not leak:
440 client.cleanup() 520 client.cleanup()
441 521
442 522
443 def main(): 523 def main():
444 verbose = '-v' in sys.argv 524 verbose = '-v' in sys.argv
525 leak = bool('--leak' in sys.argv)
526 if leak:
527 sys.argv.remove('--leak')
445 if verbose: 528 if verbose:
446 logging.basicConfig(level=logging.INFO) 529 logging.basicConfig(level=logging.INFO)
447 unittest.TestCase.maxDiff = None 530 unittest.TestCase.maxDiff = None
448 else: 531 else:
449 logging.basicConfig(level=logging.ERROR) 532 logging.basicConfig(level=logging.ERROR)
450 533
451 # Force language to be English, otherwise the error messages differ from 534 # Force language to be English, otherwise the error messages differ from
452 # expectations. 535 # expectations.
453 os.environ['LANG'] = 'en_US.UTF-8' 536 os.environ['LANG'] = 'en_US.UTF-8'
454 os.environ['LANGUAGE'] = 'en_US.UTF-8' 537 os.environ['LANGUAGE'] = 'en_US.UTF-8'
(...skipping 12 matching lines...) Expand all
467 # Test cases only interract with the client; except for test_update_continue 550 # Test cases only interract with the client; except for test_update_continue
468 # which mutates the bot. 551 # which mutates the bot.
469 Test.client = client 552 Test.client = client
470 Test.servers = servers 553 Test.servers = servers
471 failed = not unittest.main(exit=False).result.wasSuccessful() 554 failed = not unittest.main(exit=False).result.wasSuccessful()
472 except KeyboardInterrupt: 555 except KeyboardInterrupt:
473 print >> sys.stderr, '<Ctrl-C>' 556 print >> sys.stderr, '<Ctrl-C>'
474 if bot: 557 if bot:
475 bot.kill() 558 bot.kill()
476 finally: 559 finally:
477 cleanup(bot, client, servers, failed or verbose) 560 cleanup(bot, client, servers, failed or verbose, leak)
478 return int(failed) 561 return int(failed)
479 562
480 563
481 if __name__ == '__main__': 564 if __name__ == '__main__':
482 sys.exit(main()) 565 sys.exit(main())
OLDNEW
« no previous file with comments | « appengine/components/tool_support/local_app.py ('k') | appengine/swarming/swarming_bot/__main__.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698