| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2014 The LUCI Authors. All rights reserved. | 2 # Copyright 2014 The LUCI Authors. All rights reserved. |
| 3 # Use of this source code is governed under the Apache License, Version 2.0 | 3 # Use of this source code is governed under the Apache License, Version 2.0 |
| 4 # that can be found in the LICENSE file. | 4 # that can be 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 """ |
| 11 | 11 |
| 12 import base64 | 12 import base64 |
| 13 import json | 13 import json |
| 14 import glob | 14 import glob |
| 15 import logging | 15 import logging |
| 16 import os | 16 import os |
| 17 import re |
| 17 import signal | 18 import signal |
| 18 import socket | 19 import socket |
| 19 import sys | 20 import sys |
| 20 import tempfile | 21 import tempfile |
| 21 import time | 22 import time |
| 22 import unittest | 23 import unittest |
| 23 import urllib | 24 import urllib |
| 24 | 25 |
| 25 APP_DIR = os.path.dirname(os.path.abspath(__file__)) | 26 APP_DIR = os.path.dirname(os.path.abspath(__file__)) |
| 26 BOT_DIR = os.path.join(APP_DIR, 'swarming_bot') | 27 BOT_DIR = os.path.join(APP_DIR, 'swarming_bot') |
| (...skipping 240 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 267 'python', '-u', '-c', 'print(\'' + invalid_bytes + '\')', | 268 'python', '-u', '-c', 'print(\'' + invalid_bytes + '\')', |
| 268 ] | 269 ] |
| 269 summary = self.gen_expected( | 270 summary = self.gen_expected( |
| 270 name=u'non_utf8', | 271 name=u'non_utf8', |
| 271 # The string is mostly converted to 'Replacement Character'. | 272 # The string is mostly converted to 'Replacement Character'. |
| 272 outputs=[u'A\ufeff\ufffd\ufffd\ufffdsfs\ufffd(B\n']) | 273 outputs=[u'A\ufeff\ufffd\ufffd\ufffdsfs\ufffd(B\n']) |
| 273 self.assertOneTask(args, summary, {}) | 274 self.assertOneTask(args, summary, {}) |
| 274 | 275 |
| 275 def test_invalid_command(self): | 276 def test_invalid_command(self): |
| 276 args = ['-T', 'invalid', '--', 'unknown_invalid_command'] | 277 args = ['-T', 'invalid', '--', 'unknown_invalid_command'] |
| 277 err = ( | |
| 278 '[Error 2] The system cannot find the file specified' | |
| 279 if sys.platform == 'win32' else '[Errno 2] No such file or directory') | |
| 280 summary = self.gen_expected( | 278 summary = self.gen_expected( |
| 281 name=u'invalid', | 279 name=u'invalid', |
| 282 exit_codes=[1], | 280 exit_codes=[1], |
| 283 failure=True, | 281 failure=True, |
| 284 outputs=[ | 282 outputs=re.compile( |
| 285 u'Command "unknown_invalid_command" failed to start.\n' | 283 u'^<The executable does not exist or a dependent library is ' |
| 286 u'Error: %s' % err, | 284 u'missing>')) |
| 287 ]) | |
| 288 self.assertOneTask(args, summary, {}) | 285 self.assertOneTask(args, summary, {}) |
| 289 | 286 |
| 290 def test_hard_timeout(self): | 287 def test_hard_timeout(self): |
| 291 args = [ | 288 args = [ |
| 292 # Need to flush to ensure it will be sent to the server. | 289 # Need to flush to ensure it will be sent to the server. |
| 293 '-T', 'hard_timeout', '--hard-timeout', '1', '--', | 290 '-T', 'hard_timeout', '--hard-timeout', '1', '--', |
| 294 'python', '-u', '-c', | 291 'python', '-u', '-c', |
| 295 'import time,sys; sys.stdout.write(\'hi\\n\'); ' | 292 'import time,sys; sys.stdout.write(\'hi\\n\'); ' |
| 296 'sys.stdout.flush(); time.sleep(120)', | 293 'sys.stdout.flush(); time.sleep(120)', |
| 297 ] | 294 ] |
| (...skipping 212 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 510 actual_files.pop('summary.json') | 507 actual_files.pop('summary.json') |
| 511 self.assertEqual(expected_files, actual_files) | 508 self.assertEqual(expected_files, actual_files) |
| 512 finally: | 509 finally: |
| 513 file_path.rmtree(tmpdir) | 510 file_path.rmtree(tmpdir) |
| 514 | 511 |
| 515 def assertResults(self, expected, result): | 512 def assertResults(self, expected, result): |
| 516 self.assertEqual(['shards'], result.keys()) | 513 self.assertEqual(['shards'], result.keys()) |
| 517 self.assertEqual(1, len(result['shards'])) | 514 self.assertEqual(1, len(result['shards'])) |
| 518 self.assertTrue(result['shards'][0]) | 515 self.assertTrue(result['shards'][0]) |
| 519 result = result['shards'][0].copy() | 516 result = result['shards'][0].copy() |
| 517 self.assertFalse(result.get('abandoned_ts')) |
| 520 # These are not deterministic (or I'm too lazy to calculate the value). | 518 # These are not deterministic (or I'm too lazy to calculate the value). |
| 521 if expected.get('performance_stats'): | 519 if expected.get('performance_stats'): |
| 522 self.assertLess( | 520 self.assertLess( |
| 523 0, result['performance_stats'].pop('bot_overhead')) | 521 0, result['performance_stats'].pop('bot_overhead')) |
| 524 self.assertLess( | 522 self.assertLess( |
| 525 0, result['performance_stats']['isolated_download'].pop('duration')) | 523 0, result['performance_stats']['isolated_download'].pop('duration')) |
| 526 self.assertLess( | 524 self.assertLess( |
| 527 0, result['performance_stats']['isolated_upload'].pop('duration')) | 525 0, result['performance_stats']['isolated_upload'].pop('duration')) |
| 528 for k in ('isolated_download', 'isolated_upload'): | 526 for k in ('isolated_download', 'isolated_upload'): |
| 529 for j in ('items_cold', 'items_hot'): | 527 for j in ('items_cold', 'items_hot'): |
| 530 result['performance_stats'][k][j] = large.unpack( | 528 result['performance_stats'][k][j] = large.unpack( |
| 531 base64.b64decode(result['performance_stats'][k].get(j, ''))) | 529 base64.b64decode(result['performance_stats'][k].get(j, ''))) |
| 532 else: | 530 else: |
| 533 perf_stats = result.get('performance_stats') | 531 perf_stats = result.pop('performance_stats', None) |
| 534 if perf_stats: | 532 if perf_stats: |
| 535 # Ignore bot_overhead, everything else should be empty. | 533 # Ignore bot_overhead, everything else should be empty. |
| 536 perf_stats.pop('bot_overhead', None) | 534 perf_stats.pop('bot_overhead', None) |
| 537 self.assertFalse(perf_stats) | 535 self.assertFalse(perf_stats) |
| 538 | 536 |
| 539 bot_version = result.pop('bot_version') | 537 bot_version = result.pop('bot_version') |
| 540 self.assertTrue(bot_version) | 538 self.assertTrue(bot_version) |
| 541 self.assertLess(0, result.pop('costs_usd')) | 539 self.assertLess(0, result.pop('costs_usd')) |
| 542 self.assertTrue(result.pop('created_ts')) | 540 self.assertTrue(result.pop('created_ts')) |
| 543 self.assertTrue(result.pop('completed_ts')) | 541 self.assertTrue(result.pop('completed_ts')) |
| 544 self.assertLess(0, result.pop('durations')) | 542 self.assertLess(0, result.pop('durations')) |
| 545 self.assertTrue(result.pop('id')) | 543 self.assertTrue(result.pop('id')) |
| 546 self.assertTrue(result.pop('modified_ts')) | 544 self.assertTrue(result.pop('modified_ts')) |
| 547 self.assertTrue(result.pop('started_ts')) | 545 self.assertTrue(result.pop('started_ts')) |
| 546 |
| 547 if getattr(expected.get('outputs'), 'match', None): |
| 548 expected_outputs = expected.pop('outputs') |
| 549 outputs = '\n'.join(result.pop('outputs')) |
| 550 self.assertTrue( |
| 551 expected_outputs.match(outputs), |
| 552 '%s does not match %s' % (outputs, expected_outputs.pattern)) |
| 553 |
| 548 self.assertEqual(expected, result) | 554 self.assertEqual(expected, result) |
| 549 return bot_version | 555 return bot_version |
| 550 | 556 |
| 551 def assertOneTask(self, args, expected_summary, expected_files): | 557 def assertOneTask(self, args, expected_summary, expected_files): |
| 552 """Runs a single task at a time.""" | 558 """Runs a single task at a time.""" |
| 553 task_id = self.client.task_trigger_raw(args) | 559 task_id = self.client.task_trigger_raw(args) |
| 554 actual_summary, actual_files = self.client.task_collect(task_id) | 560 actual_summary, actual_files = self.client.task_collect(task_id) |
| 555 bot_version = self.assertResults(expected_summary, actual_summary) | 561 bot_version = self.assertResults(expected_summary, actual_summary) |
| 556 actual_files.pop('summary.json') | 562 actual_files.pop('summary.json') |
| 557 self.assertEqual(expected_files, actual_files) | 563 self.assertEqual(expected_files, actual_files) |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 633 if bot.poll() is None: | 639 if bot.poll() is None: |
| 634 bot.kill() | 640 bot.kill() |
| 635 bot.wait() | 641 bot.wait() |
| 636 finally: | 642 finally: |
| 637 cleanup(bot, client, servers, failed or verbose, leak) | 643 cleanup(bot, client, servers, failed or verbose, leak) |
| 638 return int(failed) | 644 return int(failed) |
| 639 | 645 |
| 640 | 646 |
| 641 if __name__ == '__main__': | 647 if __name__ == '__main__': |
| 642 sys.exit(main()) | 648 sys.exit(main()) |
| OLD | NEW |