Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 | 2 |
| 3 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. | 3 # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| 4 # Use of this source code is governed by a BSD-style license that can be | 4 # Use of this source code is governed by a BSD-style license that can be |
| 5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
| 6 | 6 |
| 7 """This module runs a suite of Auto Update tests. | 7 """This module runs a suite of Auto Update tests. |
| 8 | 8 |
| 9 The tests can be run on either a virtual machine or actual device depending | 9 The tests can be run on either a virtual machine or actual device depending |
| 10 on parameters given. Specific tests can be run by invoking --test_prefix. | 10 on parameters given. Specific tests can be run by invoking --test_prefix. |
| 11 Verbose is useful for many of the tests if you want to see individual commands | 11 Verbose is useful for many of the tests if you want to see individual commands |
| 12 being run during the update process. | 12 being run during the update process. |
| 13 """ | 13 """ |
| 14 | 14 |
| 15 import optparse | 15 import optparse |
| 16 import os | 16 import os |
| 17 import re | 17 import re |
| 18 import subprocess | |
| 18 import sys | 19 import sys |
| 19 import threading | 20 import threading |
| 20 import time | 21 import time |
| 21 import unittest | 22 import unittest |
| 22 import urllib | 23 import urllib |
| 23 | 24 |
| 24 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib')) | 25 sys.path.append(os.path.join(os.path.dirname(__file__), '../lib')) |
| 25 from cros_build_lib import Die | 26 from cros_build_lib import Die |
| 27 from cros_build_lib import GetIPAddress | |
| 26 from cros_build_lib import Info | 28 from cros_build_lib import Info |
| 27 from cros_build_lib import ReinterpretPathForChroot | 29 from cros_build_lib import ReinterpretPathForChroot |
| 28 from cros_build_lib import RunCommand | 30 from cros_build_lib import RunCommand |
| 29 from cros_build_lib import RunCommandCaptureOutput | 31 from cros_build_lib import RunCommandCaptureOutput |
| 30 from cros_build_lib import Warning | 32 from cros_build_lib import Warning |
| 31 | 33 |
| 32 import cros_test_proxy | 34 import cros_test_proxy |
| 33 | 35 |
| 36 global dev_server_cache | |
| 37 | |
| 34 | 38 |
| 35 class UpdateException(Exception): | 39 class UpdateException(Exception): |
| 36 """Exception thrown when _UpdateImage or _UpdateUsingPayload fail""" | 40 """Exception thrown when _UpdateImage or _UpdateUsingPayload fail""" |
| 37 def __init__(self, code, stdout): | 41 def __init__(self, code, stdout): |
| 38 self.code = code | 42 self.code = code |
| 39 self.stdout = stdout | 43 self.stdout = stdout |
| 40 | 44 |
| 41 | 45 |
| 42 class AUTest(object): | 46 class AUTest(object): |
| 43 """Abstract interface that defines an Auto Update test.""" | 47 """Abstract interface that defines an Auto Update test.""" |
| (...skipping 418 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 462 output = RunCommand([ | 466 output = RunCommand([ |
| 463 '%s/run_remote_tests.sh' % self.crosutils, | 467 '%s/run_remote_tests.sh' % self.crosutils, |
| 464 '--remote=%s' % self.remote, | 468 '--remote=%s' % self.remote, |
| 465 self.verify_suite, | 469 self.verify_suite, |
| 466 ], error_ok=True, enter_chroot=False, redirect_stdout=True) | 470 ], error_ok=True, enter_chroot=False, redirect_stdout=True) |
| 467 return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass) | 471 return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass) |
| 468 | 472 |
| 469 | 473 |
| 470 class VirtualAUTest(unittest.TestCase, AUTest): | 474 class VirtualAUTest(unittest.TestCase, AUTest): |
| 471 """Test harness for updating virtual machines.""" | 475 """Test harness for updating virtual machines.""" |
| 472 vm_image_path = None | |
| 473 | 476 |
| 474 # VM Constants. | 477 # VM Constants. |
| 475 _FULL_VDISK_SIZE = 6072 | 478 _FULL_VDISK_SIZE = 6072 |
| 476 _FULL_STATEFULFS_SIZE = 3074 | 479 _FULL_STATEFULFS_SIZE = 3074 |
| 477 _KVM_PID_FILE = '/tmp/harness_pid' | 480 |
| 481 # Class variables used to acquire individual VM variables per test. | |
| 482 _vm_lock = threading.Lock() | |
| 483 _next_port = 9222 | |
| 478 | 484 |
| 479 def _KillExistingVM(self, pid_file): | 485 def _KillExistingVM(self, pid_file): |
| 480 if os.path.exists(pid_file): | 486 if os.path.exists(pid_file): |
| 481 Warning('Existing %s found. Deleting and killing process' % | 487 Warning('Existing %s found. Deleting and killing process' % |
| 482 pid_file) | 488 pid_file) |
| 483 RunCommand(['./cros_stop_vm', '--kvm_pid=%s' % pid_file], | 489 RunCommand(['./cros_stop_vm', '--kvm_pid=%s' % pid_file], |
| 484 cwd=self.crosutilsbin) | 490 cwd=self.crosutilsbin) |
| 485 | 491 |
| 486 assert not os.path.exists(pid_file) | 492 assert not os.path.exists(pid_file) |
| 487 | 493 |
| 494 def _AcquireUniquePortAndPidFile(self): | |
| 495 """Acquires unique ssh port and pid file for VM.""" | |
| 496 with VirtualAUTest._vm_lock: | |
| 497 self._ssh_port = VirtualAUTest._next_port | |
| 498 self._kvm_pid_file = '/tmp/kvm.%d' % self._ssh_port | |
| 499 VirtualAUTest._next_port += 1 | |
| 500 | |
| 488 def setUp(self): | 501 def setUp(self): |
| 489 """Unit test overriden method. Is called before every test.""" | 502 """Unit test overriden method. Is called before every test.""" |
| 490 AUTest.setUp(self) | 503 AUTest.setUp(self) |
| 491 self._KillExistingVM(self._KVM_PID_FILE) | 504 self.vm_image_path = None |
| 505 self._AcquireUniquePortAndPidFile() | |
| 506 self._KillExistingVM(self._kvm_pid_file) | |
| 507 | |
| 508 def tearDown(self): | |
| 509 self._KillExistingVM(self._kvm_pid_file) | |
| 492 | 510 |
| 493 @classmethod | 511 @classmethod |
| 494 def ProcessOptions(cls, parser, options): | 512 def ProcessOptions(cls, parser, options): |
| 495 """Processes vm-specific options.""" | 513 """Processes vm-specific options.""" |
| 496 AUTest.ProcessOptions(parser, options) | 514 AUTest.ProcessOptions(parser, options) |
| 497 cls.board = options.board | 515 cls.board = options.board |
| 498 | 516 |
| 499 # Communicate flags to tests. | 517 # Communicate flags to tests. |
| 500 cls.graphics_flag = '' | 518 cls.graphics_flag = '' |
| 501 if options.no_graphics: cls.graphics_flag = '--no_graphics' | 519 if options.no_graphics: cls.graphics_flag = '--no_graphics' |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 520 os.path.dirname(image_path)), | 538 os.path.dirname(image_path)), |
| 521 '--vdisk_size=%s' % self._FULL_VDISK_SIZE, | 539 '--vdisk_size=%s' % self._FULL_VDISK_SIZE, |
| 522 '--statefulfs_size=%s' % self._FULL_STATEFULFS_SIZE, | 540 '--statefulfs_size=%s' % self._FULL_STATEFULFS_SIZE, |
| 523 '--board=%s' % self.board, | 541 '--board=%s' % self.board, |
| 524 '--test_image'], enter_chroot=True) | 542 '--test_image'], enter_chroot=True) |
| 525 | 543 |
| 526 Info('Using %s as base' % self.vm_image_path) | 544 Info('Using %s as base' % self.vm_image_path) |
| 527 self.assertTrue(os.path.exists(self.vm_image_path)) | 545 self.assertTrue(os.path.exists(self.vm_image_path)) |
| 528 | 546 |
| 529 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', | 547 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', |
| 530 proxy_port=None): | 548 proxy_port=''): |
| 531 """Updates VM image with image_path.""" | 549 """Updates VM image with image_path.""" |
| 532 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) | 550 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) |
| 533 if src_image_path and self._first_update: | 551 if src_image_path and self._first_update: |
| 534 src_image_path = self.vm_image_path | 552 src_image_path = self.vm_image_path |
| 535 self._first_update = False | 553 self._first_update = False |
| 536 | 554 |
| 537 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, | 555 # Check image payload cache first. |
| 556 update_id = _GenerateUpdateId(target=image_path, src=src_image_path) | |
| 557 cache_path = dev_server_cache[update_id] | |
| 558 if cache_path: | |
| 559 Info('Using cache %s' % cache_path) | |
| 560 update_url = DevServerWrapper.GetDevServerURL(proxy_port, cache_path) | |
| 561 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, | |
|
dgarrett
2011/01/25 03:44:02
Indention is off.
| |
| 562 '--vm_image_path=%s' % self.vm_image_path, | |
| 563 '--snapshot', | |
| 564 self.graphics_flag, | |
| 565 '--persist', | |
| 566 '--kvm_pid=%s' % self._kvm_pid_file, | |
| 567 '--ssh_port=%s' % self._ssh_port, | |
| 568 stateful_change_flag, | |
| 569 '--update_url=%s' % update_url, | |
| 570 ] | |
| 571 else: | |
| 572 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, | |
|
dgarrett
2011/01/25 03:44:02
More indention is off.
| |
| 538 '--update_image_path=%s' % image_path, | 573 '--update_image_path=%s' % image_path, |
| 539 '--vm_image_path=%s' % self.vm_image_path, | 574 '--vm_image_path=%s' % self.vm_image_path, |
| 540 '--snapshot', | 575 '--snapshot', |
| 541 self.graphics_flag, | 576 self.graphics_flag, |
| 542 '--persist', | 577 '--persist', |
| 543 '--kvm_pid=%s' % self._KVM_PID_FILE, | 578 '--kvm_pid=%s' % self._kvm_pid_file, |
| 579 '--ssh_port=%s' % self._ssh_port, | |
| 544 stateful_change_flag, | 580 stateful_change_flag, |
| 545 '--src_image=%s' % src_image_path, | 581 '--src_image=%s' % src_image_path, |
| 582 '--proxy_port=%s' % proxy_port | |
| 546 ] | 583 ] |
| 547 | 584 |
| 548 if proxy_port: | |
| 549 cmd.append('--proxy_port=%s' % proxy_port) | |
| 550 | |
| 551 if self.verbose: | 585 if self.verbose: |
| 552 try: | 586 try: |
| 553 RunCommand(cmd) | 587 RunCommand(cmd) |
| 554 except Exception, e: | 588 except Exception, e: |
| 555 raise UpdateException(1, e.message) | 589 raise UpdateException(1, e.message) |
| 556 else: | 590 else: |
| 557 (code, stdout, stderr) = RunCommandCaptureOutput(cmd) | 591 (code, stdout, stderr) = RunCommandCaptureOutput(cmd) |
| 558 if code != 0: | 592 if code != 0: |
| 559 raise UpdateException(code, stdout) | 593 raise UpdateException(code, stdout) |
| 560 | 594 |
| 561 def _UpdateUsingPayload(self, update_path, stateful_change='old', | 595 def _UpdateUsingPayload(self, update_path, stateful_change='old', |
| 562 proxy_port=None): | 596 proxy_port=None): |
| 563 """Updates a vm image using cros_run_vm_update.""" | 597 """Updates a vm image using cros_run_vm_update.""" |
| 564 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) | 598 stateful_change_flag = self.GetStatefulChangeFlag(stateful_change) |
| 565 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, | 599 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, |
| 566 '--payload=%s' % update_path, | 600 '--payload=%s' % update_path, |
| 567 '--vm_image_path=%s' % self.vm_image_path, | 601 '--vm_image_path=%s' % self.vm_image_path, |
| 568 '--snapshot', | 602 '--snapshot', |
| 569 self.graphics_flag, | 603 self.graphics_flag, |
| 570 '--persist', | 604 '--persist', |
| 571 '--kvm_pid=%s' % self._KVM_PID_FILE, | 605 '--kvm_pid=%s' % self._kvm_pid_file, |
| 606 '--ssh_port=%s' % self._ssh_port, | |
| 572 stateful_change_flag, | 607 stateful_change_flag, |
| 573 ] | 608 ] |
| 574 | 609 |
| 575 if proxy_port: | 610 if proxy_port: |
| 576 cmd.append('--proxy_port=%s' % proxy_port) | 611 cmd.append('--proxy_port=%s' % proxy_port) |
| 577 | 612 |
| 578 if self.verbose: | 613 if self.verbose: |
| 579 try: | 614 try: |
| 580 RunCommand(cmd) | 615 RunCommand(cmd) |
| 581 except Exception, e: | 616 except Exception, e: |
| 582 raise UpdateException(1, e.message) | 617 raise UpdateException(1, e.message) |
| 583 else: | 618 else: |
| 584 (code, stdout, stderr) = RunCommandCaptureOutput(cmd) | 619 (code, stdout, stderr) = RunCommandCaptureOutput(cmd) |
| 585 if code != 0: | 620 if code != 0: |
| 586 raise UpdateException(code, stdout) | 621 raise UpdateException(code, stdout) |
| 587 | 622 |
| 588 def VerifyImage(self, percent_required_to_pass): | 623 def VerifyImage(self, percent_required_to_pass): |
| 589 """Runs vm smoke suite to verify image.""" | 624 """Runs vm smoke suite to verify image.""" |
| 590 # image_to_live already verifies lsb-release matching. This is just | 625 # image_to_live already verifies lsb-release matching. This is just |
| 591 # for additional steps. | 626 # for additional steps. |
| 592 | 627 |
| 593 commandWithArgs = ['%s/cros_run_vm_test' % self.crosutilsbin, | 628 commandWithArgs = ['%s/cros_run_vm_test' % self.crosutilsbin, |
| 594 '--image_path=%s' % self.vm_image_path, | 629 '--image_path=%s' % self.vm_image_path, |
| 595 '--snapshot', | 630 '--snapshot', |
| 596 '--persist', | 631 '--persist', |
| 597 '--kvm_pid=%s' % self._KVM_PID_FILE, | 632 '--kvm_pid=%s' % self._kvm_pid_file, |
| 633 '--ssh_port=%s' % self._ssh_port, | |
| 598 self.verify_suite, | 634 self.verify_suite, |
| 599 ] | 635 ] |
| 600 | 636 |
| 601 if self.graphics_flag: | 637 if self.graphics_flag: |
| 602 commandWithArgs.append(self.graphics_flag) | 638 commandWithArgs.append(self.graphics_flag) |
| 603 | 639 |
| 604 output = RunCommand(commandWithArgs, error_ok=True, enter_chroot=False, | 640 output = RunCommand(commandWithArgs, error_ok=True, enter_chroot=False, |
| 605 redirect_stdout=True) | 641 redirect_stdout=True) |
| 606 return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass) | 642 return self.AssertEnoughTestsPassed(self, output, percent_required_to_pass) |
| 607 | 643 |
| 608 | 644 |
| 609 class GenerateVirtualAUDeltasTest(VirtualAUTest): | 645 class GenerateVirtualAUDeltasTest(VirtualAUTest): |
| 610 """Class the overrides VirtualAUTest and stores deltas we will generate.""" | 646 """Class the overrides VirtualAUTest and stores deltas we will generate.""" |
| 611 delta_list = {} | 647 delta_list = {} |
| 612 | 648 |
| 613 def setUp(self): | 649 def setUp(self): |
| 614 AUTest.setUp(self) | 650 AUTest.setUp(self) |
| 615 | 651 |
| 652 def tearDown(self): | |
| 653 pass | |
| 654 | |
| 616 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', | 655 def _UpdateImage(self, image_path, src_image_path='', stateful_change='old', |
| 617 proxy_port=None): | 656 proxy_port=None): |
| 618 if src_image_path and self._first_update: | 657 if src_image_path and self._first_update: |
| 619 src_image_path = self.vm_image_path | 658 src_image_path = self.vm_image_path |
| 620 self._first_update = False | 659 self._first_update = False |
| 621 | 660 |
| 622 image_path = ReinterpretPathForChroot(image_path) | |
| 623 if src_image_path: | |
| 624 src_image_path = ReinterpretPathForChroot(src_image_path) | |
| 625 if not self.delta_list.has_key(image_path): | 661 if not self.delta_list.has_key(image_path): |
| 626 self.delta_list[image_path] = set([src_image_path]) | 662 self.delta_list[image_path] = set([src_image_path]) |
| 627 else: | 663 else: |
| 628 self.delta_list[image_path].add(src_image_path) | 664 self.delta_list[image_path].add(src_image_path) |
| 629 | 665 |
| 630 def AttemptUpdateWithPayloadExpectedFailure(self, payload, expected_msg): | 666 def AttemptUpdateWithPayloadExpectedFailure(self, payload, expected_msg): |
| 631 pass | 667 pass |
| 632 | 668 |
| 633 def VerifyImage(self, percent_required_to_pass): | 669 def VerifyImage(self, percent_required_to_pass): |
| 634 pass | 670 pass |
| 635 | 671 |
| 636 | 672 |
| 637 class ParallelJob(threading.Thread): | 673 class ParallelJob(threading.Thread): |
| 638 """Small wrapper for threading.Thread that releases a semaphore on exit.""" | 674 """Small wrapper for threading.Thread that releases a semaphores on exit.""" |
|
dgarrett
2011/01/25 03:44:02
Space after period.
| |
| 639 def __init__(self, semaphore, target, args): | 675 |
| 676 def __init__(self, starting_semaphore, ending_semaphore, target, args): | |
| 677 """Initializes an instance of a job. | |
| 678 | |
| 679 Args: | |
| 680 starting_semaphore: Semaphore used by caller to wait on such that | |
| 681 there isn't more than a certain number of threads running. Should | |
| 682 be initialized to a value for the number of threads wanting to be run | |
| 683 at a time. | |
| 684 ending_semaphore: Semaphore is released every time a job ends. Should be | |
|
dgarrett
2011/01/25 03:44:02
I think it's only one space after the ':', and sur
| |
| 685 initialized to 0 before starting first job. Should be acquired once for | |
| 686 each job. Threading.Thread.join() has a bug where if the run function | |
| 687 terminates too quickly join() will hang forever. | |
| 688 target: The func to run. | |
| 689 args: Args to pass to the fun. | |
| 690 """ | |
| 640 threading.Thread.__init__(self, target=target, args=args) | 691 threading.Thread.__init__(self, target=target, args=args) |
| 641 self._target = target | 692 self._target = target |
| 642 self._args = args | 693 self._args = args |
| 643 self._semaphore = semaphore | 694 self._starting_semaphore = starting_semaphore |
| 695 self._ending_semaphore = ending_semaphore | |
| 644 self._output = None | 696 self._output = None |
| 645 self._completed = False | 697 self._completed = False |
| 646 | 698 |
| 647 def run(self): | 699 def run(self): |
| 700 """Thread override. Runs the method specified and sets output.""" | |
|
dgarrett
2011/01/25 03:44:02
Two spaces after period.
| |
| 648 try: | 701 try: |
| 649 threading.Thread.run(self) | 702 self._output = self._target(*self._args) |
| 650 finally: | 703 finally: |
| 704 # From threading.py to avoid a refcycle. | |
| 705 del self._target, self._args | |
| 706 # Our own clean up. | |
| 651 self._Cleanup() | 707 self._Cleanup() |
| 652 self._completed = True | 708 self._completed = True |
| 653 | 709 |
| 654 def GetOutput(self): | 710 def GetOutput(self): |
| 711 """Returns the output of the method run.""" | |
| 655 assert self._completed, 'GetOutput called before thread was run.' | 712 assert self._completed, 'GetOutput called before thread was run.' |
| 656 return self._output | 713 return self._output |
| 657 | 714 |
| 658 def _Cleanup(self): | 715 def _Cleanup(self): |
| 659 self._semaphore.release() | 716 """Releases semaphores for a waiting caller.""" |
| 717 self._starting_semaphore.release() | |
| 718 self._ending_semaphore.release() | |
| 660 | 719 |
| 661 def __str__(self): | 720 def __str__(self): |
| 662 return '%s(%s)' % (self._target, self._args) | 721 return '%s(%s)' % (self._target, self._args) |
| 663 | 722 |
| 664 | 723 |
| 724 class DevServerWrapper(threading.Thread): | |
| 725 """A Simple wrapper around a devserver instance.""" | |
| 726 | |
| 727 def __init__(self): | |
| 728 self.proc = None | |
| 729 threading.Thread.__init__(self) | |
| 730 | |
| 731 def run(self): | |
| 732 # Kill previous running instance of devserver if it exists. | |
| 733 RunCommand(['sudo', 'pkill', '-f', 'devserver.py', ], error_ok=True, | |
| 734 print_cmd=False) | |
| 735 self.proc = subprocess.Popen(['./start_devserver', | |
| 736 '--archive_dir=./static', | |
| 737 '--client_prefix=ChromeOSUpdateEngine', | |
| 738 '--production', | |
| 739 ]) | |
| 740 self.proc.communicate() | |
| 741 | |
| 742 def Stop(self): | |
| 743 """Kills the devserver instance.""" | |
| 744 self.proc.kill() | |
| 745 | |
| 746 @classmethod | |
| 747 def GetDevServerURL(cls, port, sub_dir): | |
| 748 """Returns the dev server url for a given port and sub directory.""" | |
| 749 ip_addr = GetIPAddress() | |
| 750 if not port: port = 8080 | |
| 751 url = 'http://%(ip)s:%(port)s/%(dir)s' % {'ip': ip_addr, | |
| 752 'port': str(port), | |
| 753 'dir': sub_dir} | |
| 754 return url | |
| 755 | |
| 756 | |
| 757 def _GenerateUpdateId(target, src): | |
| 758 """Returns a simple representation id of target and src paths.""" | |
| 759 if src: | |
| 760 return '%s->%s' % (target, src) | |
| 761 else: | |
| 762 return target | |
| 763 | |
| 764 | |
| 665 def _RunParallelJobs(number_of_sumultaneous_jobs, jobs, jobs_args): | 765 def _RunParallelJobs(number_of_sumultaneous_jobs, jobs, jobs_args): |
| 666 """Runs set number of specified jobs in parallel. | 766 """Runs set number of specified jobs in parallel. |
| 667 | 767 |
| 668 Args: | 768 Args: |
| 669 number_of_simultaneous_jobs: Max number of threads to be run in parallel. | 769 number_of_simultaneous_jobs: Max number of threads to be run in parallel. |
| 670 jobs: Array of methods to run. | 770 jobs: Array of methods to run. |
| 671 jobs_args: Array of args associated with method calls. | 771 jobs_args: Array of args associated with method calls. |
| 672 Returns: | 772 Returns: |
| 673 Returns an array of results corresponding to each thread. | 773 Returns an array of results corresponding to each thread. |
| 674 """ | 774 """ |
| 675 def _TwoTupleize(x, y): | 775 def _TwoTupleize(x, y): |
| 676 return (x, y) | 776 return (x, y) |
| 677 | 777 |
| 678 threads = [] | 778 threads = [] |
| 679 job_pool_semaphore = threading.Semaphore(number_of_sumultaneous_jobs) | 779 job_start_semaphore = threading.Semaphore(number_of_sumultaneous_jobs) |
| 780 join_semaphore = threading.Semaphore(0) | |
| 680 assert len(jobs) == len(jobs_args), 'Length of args array is wrong.' | 781 assert len(jobs) == len(jobs_args), 'Length of args array is wrong.' |
| 681 | 782 |
| 682 # Create the parallel jobs. | 783 # Create the parallel jobs. |
| 683 for job, args in map(_TwoTupleize, jobs, jobs_args): | 784 for job, args in map(_TwoTupleize, jobs, jobs_args): |
| 684 thread = ParallelJob(job_pool_semaphore, target=job, args=args) | 785 thread = ParallelJob(job_start_semaphore, join_semaphore, target=job, |
| 786 args=args) | |
| 685 threads.append(thread) | 787 threads.append(thread) |
| 686 | 788 |
| 687 # We use a semaphore to ensure we don't run more jobs that required. | 789 # We use a semaphore to ensure we don't run more jobs that required. |
| 688 # After each thread finishes, it releases (increments semaphore). | 790 # After each thread finishes, it releases (increments semaphore). |
| 689 # Acquire blocks of num jobs reached and continues when a thread finishes. | 791 # Acquire blocks of num jobs reached and continues when a thread finishes. |
| 690 for next_thread in threads: | 792 for next_thread in threads: |
| 691 job_pool_semaphore.acquire(blocking=True) | 793 job_start_semaphore.acquire(blocking=True) |
| 692 Info('Starting %s' % next_thread) | 794 Info('Starting job %s' % next_thread) |
| 693 next_thread.start() | 795 next_thread.start() |
| 694 | 796 |
| 695 # Wait on the rest of the threads to finish. | 797 # Wait on the rest of the threads to finish. |
| 696 for thread in threads: | 798 for thread in threads: |
| 697 thread.join() | 799 join_semaphore.acquire(blocking=True) |
| 698 | 800 |
| 699 return [thread.GetOutput() for thread in threads] | 801 return [thread.GetOutput() for thread in threads] |
| 700 | 802 |
| 701 | 803 |
| 804 def _PrepareTestSuite(parser, options, test_class): | |
| 805 """Returns a prepared test suite given by the options and test class.""" | |
| 806 test_class.ProcessOptions(parser, options) | |
| 807 test_loader = unittest.TestLoader() | |
| 808 test_loader.testMethodPrefix = options.test_prefix | |
| 809 return test_loader.loadTestsFromTestCase(test_class) | |
| 810 | |
| 811 | |
| 702 def _PregenerateUpdates(parser, options): | 812 def _PregenerateUpdates(parser, options): |
| 703 """Determines all deltas that will be generated and generates them. | 813 """Determines all deltas that will be generated and generates them. |
| 704 | 814 |
| 705 This method effectively pre-generates the dev server cache for all tests. | 815 This method effectively pre-generates the dev server cache for all tests. |
| 706 | 816 |
| 707 Args: | 817 Args: |
| 708 parser: parser from main. | 818 parser: parser from main. |
| 709 options: options from parsed parser. | 819 options: options from parsed parser. |
| 710 Returns: | 820 Returns: |
| 711 Array of output from generating updates. | 821 Dictionary of Update Identifiers->Relative cache locations. |
| 712 """ | 822 """ |
| 713 def _GenerateVMUpdate(target, src): | 823 def _GenerateVMUpdate(target, src): |
| 714 """Generates an update using the devserver.""" | 824 """Generates an update using the devserver.""" |
| 715 RunCommand(['sudo', | 825 target = ReinterpretPathForChroot(target) |
| 716 './start_devserver', | 826 if src: |
| 717 '--pregenerate_update', | 827 src = ReinterpretPathForChroot(src) |
| 718 '--exit', | 828 |
| 719 '--image=%s' % target, | 829 return RunCommand(['sudo', |
| 720 '--src_image=%s' % src, | 830 './start_devserver', |
| 721 '--for_vm' | 831 '--pregenerate_update', |
| 722 ], enter_chroot=True) | 832 '--exit', |
| 833 '--image=%s' % target, | |
| 834 '--src_image=%s' % src, | |
| 835 '--for_vm', | |
| 836 ], redirect_stdout=True, enter_chroot=True, | |
| 837 print_cmd=False) | |
| 723 | 838 |
| 724 # Get the list of deltas by mocking out update method in test class. | 839 # Get the list of deltas by mocking out update method in test class. |
| 725 GenerateVirtualAUDeltasTest.ProcessOptions(parser, options) | 840 test_suite = _PrepareTestSuite(parser, options, GenerateVirtualAUDeltasTest) |
| 726 test_loader = unittest.TestLoader() | |
| 727 test_loader.testMethodPrefix = options.test_prefix | |
| 728 test_suite = test_loader.loadTestsFromTestCase(GenerateVirtualAUDeltasTest) | |
| 729 test_result = unittest.TextTestRunner(verbosity=0).run(test_suite) | 841 test_result = unittest.TextTestRunner(verbosity=0).run(test_suite) |
| 730 | 842 |
| 731 Info('The following delta updates are required.') | 843 Info('The following delta updates are required.') |
| 844 update_ids = [] | |
| 732 jobs = [] | 845 jobs = [] |
| 733 args = [] | 846 args = [] |
| 734 for target, srcs in GenerateVirtualAUDeltasTest.delta_list.items(): | 847 for target, srcs in GenerateVirtualAUDeltasTest.delta_list.items(): |
| 735 for src in srcs: | 848 for src in srcs: |
| 736 if src: | 849 update_id = _GenerateUpdateId(target=target, src=src) |
| 737 print >> sys.stderr, 'DELTA AU %s -> %s' % (src, target) | 850 print >> sys.stderr, 'AU: %s' % update_id |
| 738 else: | 851 update_ids.append(update_id) |
| 739 print >> sys.stderr, 'FULL AU %s' % target | |
| 740 | |
| 741 jobs.append(_GenerateVMUpdate) | 852 jobs.append(_GenerateVMUpdate) |
| 742 args.append((target, src)) | 853 args.append((target, src)) |
| 743 | 854 |
| 744 results = _RunParallelJobs(options.jobs, jobs, args) | 855 raw_results = _RunParallelJobs(options.jobs, jobs, args) |
| 745 return results | 856 results = [] |
| 857 | |
| 858 # Parse the output. | |
| 859 key_line_re = re.compile('^PREGENERATED_UPDATE=([\w/.]+)') | |
| 860 for result in raw_results: | |
| 861 for line in result.splitlines(): | |
| 862 match = key_line_re.search(line) | |
| 863 if match: | |
| 864 # Convert blah/blah/update.gz -> update/blah/blah. | |
| 865 path_to_update_gz = match.group(1).rstrip() | |
| 866 (path_to_update_dir, _, _) = path_to_update_gz.rpartition('/update.gz') | |
| 867 results.append('/'.join(['update', path_to_update_dir])) | |
| 868 break | |
| 869 | |
| 870 assert len(raw_results) == len(results), \ | |
| 871 'Insufficient number cache directories returned.' | |
| 872 | |
| 873 # Build the dictionary from our id's and returned cache paths. | |
| 874 cache_dictionary = {} | |
| 875 for index, id in enumerate(update_ids): | |
| 876 cache_dictionary[id] = results[index] | |
| 877 | |
| 878 return cache_dictionary | |
| 879 | |
| 880 | |
| 881 def _RunTestsInParallel(parser, options, test_class): | |
| 882 """Runs the tests given by the options and test_class in parallel.""" | |
| 883 threads = [] | |
| 884 args = [] | |
| 885 test_suite = _PrepareTestSuite(parser, options, test_class) | |
| 886 for test in test_suite: | |
| 887 test_name = test.id() | |
| 888 test_case = unittest.TestLoader().loadTestsFromName(test_name) | |
| 889 threads.append(unittest.TextTestRunner().run) | |
| 890 args.append(test_case) | |
| 891 | |
| 892 results = _RunParallelJobs(options.jobs, threads, args) | |
| 893 if not (test_result.wasSuccessful() for test_result in results): | |
| 894 Die('Test harness was not successful') | |
| 746 | 895 |
| 747 | 896 |
| 748 def main(): | 897 def main(): |
| 749 parser = optparse.OptionParser() | 898 parser = optparse.OptionParser() |
| 750 parser.add_option('-b', '--base_image', | 899 parser.add_option('-b', '--base_image', |
| 751 help='path to the base image.') | 900 help='path to the base image.') |
| 752 parser.add_option('-r', '--board', | 901 parser.add_option('-r', '--board', |
| 753 help='board for the images.') | 902 help='board for the images.') |
| 754 parser.add_option('--no_delta', action='store_false', default=True, | 903 parser.add_option('--no_delta', action='store_false', default=True, |
| 755 dest='delta', | 904 dest='delta', |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 770 parser.add_option('-p', '--type', default='vm', | 919 parser.add_option('-p', '--type', default='vm', |
| 771 help='type of test to run: [vm, real]. Default: vm.') | 920 help='type of test to run: [vm, real]. Default: vm.') |
| 772 parser.add_option('--verbose', default=True, action='store_true', | 921 parser.add_option('--verbose', default=True, action='store_true', |
| 773 help='Print out rather than capture output as much as ' | 922 help='Print out rather than capture output as much as ' |
| 774 'possible.') | 923 'possible.') |
| 775 (options, leftover_args) = parser.parse_args() | 924 (options, leftover_args) = parser.parse_args() |
| 776 | 925 |
| 777 if leftover_args: | 926 if leftover_args: |
| 778 parser.error('Found extra options we do not support: %s' % leftover_args) | 927 parser.error('Found extra options we do not support: %s' % leftover_args) |
| 779 | 928 |
| 929 # Figure out the test_class. | |
| 780 if options.type == 'vm': test_class = VirtualAUTest | 930 if options.type == 'vm': test_class = VirtualAUTest |
| 781 elif options.type == 'real': test_class = RealAUTest | 931 elif options.type == 'real': test_class = RealAUTest |
| 782 else: parser.error('Could not parse harness type %s.' % options.type) | 932 else: parser.error('Could not parse harness type %s.' % options.type) |
| 783 | 933 |
| 784 # TODO(sosa): Caching doesn't really make sense on non-vm images (yet). | 934 # TODO(sosa): Caching doesn't really make sense on non-vm images (yet). |
| 785 if options.type == 'vm': | 935 global dev_server_cache |
| 786 _PregenerateUpdates(parser, options) | 936 if options.type == 'vm' and options.jobs > 1: |
| 937 dev_server_cache = _PregenerateUpdates(parser, options) | |
| 938 my_server = DevServerWrapper() | |
| 939 my_server.start() | |
| 940 try: | |
| 941 _RunTestsInParallel(parser, options, test_class) | |
| 942 finally: | |
| 943 my_server.Stop() | |
| 787 | 944 |
| 788 # Run the test suite. | 945 else: |
| 789 test_class.ProcessOptions(parser, options) | 946 dev_server_cache = None |
| 790 test_loader = unittest.TestLoader() | 947 test_suite = _PrepareTestSuite(parser, options, test_class) |
| 791 test_loader.testMethodPrefix = options.test_prefix | 948 test_result = unittest.TextTestRunner(verbosity=2).run(test_suite) |
| 792 test_suite = test_loader.loadTestsFromTestCase(test_class) | 949 if not test_result.wasSuccessful(): |
| 793 test_result = unittest.TextTestRunner(verbosity=2).run(test_suite) | 950 Die('Test harness was not successful.') |
| 794 if not test_result.wasSuccessful(): | |
| 795 Die('Test harness was not successful.') | |
| 796 | 951 |
| 797 | 952 |
| 798 if __name__ == '__main__': | 953 if __name__ == '__main__': |
| 799 main() | 954 main() |
| OLD | NEW |