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