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. |
538 '--update_image_path=%s' % image_path, | 556 update_id = _GenerateUpdateId(target=image_path, src=src_image_path) |
539 '--vm_image_path=%s' % self.vm_image_path, | 557 cache_path = dev_server_cache[update_id] |
540 '--snapshot', | 558 if cache_path: |
541 self.graphics_flag, | 559 Info('Using cache %s' % cache_path) |
542 '--persist', | 560 update_url = DevServerWrapper.GetDevServerURL(proxy_port, cache_path) |
543 '--kvm_pid=%s' % self._KVM_PID_FILE, | 561 cmd = ['%s/cros_run_vm_update' % self.crosutilsbin, |
544 stateful_change_flag, | 562 '--vm_image_path=%s' % self.vm_image_path, |
545 '--src_image=%s' % src_image_path, | 563 '--snapshot', |
546 ] | 564 self.graphics_flag, |
547 | 565 '--persist', |
548 if proxy_port: | 566 '--kvm_pid=%s' % self._kvm_pid_file, |
549 cmd.append('--proxy_port=%s' % proxy_port) | 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, | |
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 ] | |
550 | 584 |
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.""" |
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 | |
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.""" | |
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 RunCommand(['sudo', | |
dgarrett
2011/01/26 19:19:16
Should this also be error_ok, to prevent nasty log
sosa
2011/01/26 19:22:18
I thought maybe too ... but it seems that cherrypy
| |
736 './start_devserver', | |
737 '--archive_dir=./static', | |
738 '--client_prefix=ChromeOSUpdateEngine', | |
739 '--production', | |
740 ], enter_chroot=True, print_cmd=False) | |
741 | |
742 def Stop(self): | |
743 """Kills the devserver instance.""" | |
744 RunCommand(['sudo', 'pkill', '-f', 'devserver.py'], error_ok=True, | |
745 print_cmd=False) | |
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 | |
665 def _RunParallelJobs(number_of_sumultaneous_jobs, jobs, jobs_args): | 766 def _RunParallelJobs(number_of_sumultaneous_jobs, jobs, jobs_args): |
666 """Runs set number of specified jobs in parallel. | 767 """Runs set number of specified jobs in parallel. |
667 | 768 |
668 Args: | 769 Args: |
669 number_of_simultaneous_jobs: Max number of threads to be run in parallel. | 770 number_of_simultaneous_jobs: Max number of threads to be run in parallel. |
670 jobs: Array of methods to run. | 771 jobs: Array of methods to run. |
671 jobs_args: Array of args associated with method calls. | 772 jobs_args: Array of args associated with method calls. |
672 Returns: | 773 Returns: |
673 Returns an array of results corresponding to each thread. | 774 Returns an array of results corresponding to each thread. |
674 """ | 775 """ |
675 def _TwoTupleize(x, y): | 776 def _TwoTupleize(x, y): |
676 return (x, y) | 777 return (x, y) |
677 | 778 |
678 threads = [] | 779 threads = [] |
679 job_pool_semaphore = threading.Semaphore(number_of_sumultaneous_jobs) | 780 job_start_semaphore = threading.Semaphore(number_of_sumultaneous_jobs) |
781 join_semaphore = threading.Semaphore(0) | |
680 assert len(jobs) == len(jobs_args), 'Length of args array is wrong.' | 782 assert len(jobs) == len(jobs_args), 'Length of args array is wrong.' |
681 | 783 |
682 # Create the parallel jobs. | 784 # Create the parallel jobs. |
683 for job, args in map(_TwoTupleize, jobs, jobs_args): | 785 for job, args in map(_TwoTupleize, jobs, jobs_args): |
684 thread = ParallelJob(job_pool_semaphore, target=job, args=args) | 786 thread = ParallelJob(job_start_semaphore, join_semaphore, target=job, |
787 args=args) | |
685 threads.append(thread) | 788 threads.append(thread) |
686 | 789 |
687 # We use a semaphore to ensure we don't run more jobs that required. | 790 # We use a semaphore to ensure we don't run more jobs that required. |
688 # After each thread finishes, it releases (increments semaphore). | 791 # After each thread finishes, it releases (increments semaphore). |
689 # Acquire blocks of num jobs reached and continues when a thread finishes. | 792 # Acquire blocks of num jobs reached and continues when a thread finishes. |
690 for next_thread in threads: | 793 for next_thread in threads: |
691 job_pool_semaphore.acquire(blocking=True) | 794 job_start_semaphore.acquire(blocking=True) |
692 Info('Starting %s' % next_thread) | 795 Info('Starting job %s' % next_thread) |
693 next_thread.start() | 796 next_thread.start() |
694 | 797 |
695 # Wait on the rest of the threads to finish. | 798 # Wait on the rest of the threads to finish. |
696 for thread in threads: | 799 for thread in threads: |
697 thread.join() | 800 join_semaphore.acquire(blocking=True) |
698 | 801 |
699 return [thread.GetOutput() for thread in threads] | 802 return [thread.GetOutput() for thread in threads] |
700 | 803 |
701 | 804 |
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 | |
702 def _PregenerateUpdates(parser, options): | 813 def _PregenerateUpdates(parser, options): |
703 """Determines all deltas that will be generated and generates them. | 814 """Determines all deltas that will be generated and generates them. |
704 | 815 |
705 This method effectively pre-generates the dev server cache for all tests. | 816 This method effectively pre-generates the dev server cache for all tests. |
706 | 817 |
707 Args: | 818 Args: |
708 parser: parser from main. | 819 parser: parser from main. |
709 options: options from parsed parser. | 820 options: options from parsed parser. |
710 Returns: | 821 Returns: |
711 Array of output from generating updates. | 822 Dictionary of Update Identifiers->Relative cache locations. |
712 """ | 823 """ |
713 def _GenerateVMUpdate(target, src): | 824 def _GenerateVMUpdate(target, src): |
714 """Generates an update using the devserver.""" | 825 """Generates an update using the devserver.""" |
715 RunCommand(['sudo', | 826 target = ReinterpretPathForChroot(target) |
716 './start_devserver', | 827 if src: |
717 '--pregenerate_update', | 828 src = ReinterpretPathForChroot(src) |
718 '--exit', | 829 |
719 '--image=%s' % target, | 830 return RunCommand(['sudo', |
720 '--src_image=%s' % src, | 831 './start_devserver', |
721 '--for_vm' | 832 '--pregenerate_update', |
722 ], enter_chroot=True) | 833 '--exit', |
834 '--image=%s' % target, | |
835 '--src_image=%s' % src, | |
836 '--for_vm', | |
837 ], redirect_stdout=True, enter_chroot=True, | |
838 print_cmd=False) | |
723 | 839 |
724 # Get the list of deltas by mocking out update method in test class. | 840 # Get the list of deltas by mocking out update method in test class. |
725 GenerateVirtualAUDeltasTest.ProcessOptions(parser, options) | 841 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) | 842 test_result = unittest.TextTestRunner(verbosity=0).run(test_suite) |
730 | 843 |
731 Info('The following delta updates are required.') | 844 Info('The following delta updates are required.') |
845 update_ids = [] | |
732 jobs = [] | 846 jobs = [] |
733 args = [] | 847 args = [] |
734 for target, srcs in GenerateVirtualAUDeltasTest.delta_list.items(): | 848 for target, srcs in GenerateVirtualAUDeltasTest.delta_list.items(): |
735 for src in srcs: | 849 for src in srcs: |
736 if src: | 850 update_id = _GenerateUpdateId(target=target, src=src) |
737 print >> sys.stderr, 'DELTA AU %s -> %s' % (src, target) | 851 print >> sys.stderr, 'AU: %s' % update_id |
738 else: | 852 update_ids.append(update_id) |
739 print >> sys.stderr, 'FULL AU %s' % target | |
740 | |
741 jobs.append(_GenerateVMUpdate) | 853 jobs.append(_GenerateVMUpdate) |
742 args.append((target, src)) | 854 args.append((target, src)) |
743 | 855 |
744 results = _RunParallelJobs(options.jobs, jobs, args) | 856 raw_results = _RunParallelJobs(options.jobs, jobs, args) |
745 return results | 857 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') | |
746 | 896 |
747 | 897 |
748 def main(): | 898 def main(): |
749 parser = optparse.OptionParser() | 899 parser = optparse.OptionParser() |
750 parser.add_option('-b', '--base_image', | 900 parser.add_option('-b', '--base_image', |
751 help='path to the base image.') | 901 help='path to the base image.') |
752 parser.add_option('-r', '--board', | 902 parser.add_option('-r', '--board', |
753 help='board for the images.') | 903 help='board for the images.') |
754 parser.add_option('--no_delta', action='store_false', default=True, | 904 parser.add_option('--no_delta', action='store_false', default=True, |
755 dest='delta', | 905 dest='delta', |
(...skipping 14 matching lines...) Expand all Loading... | |
770 parser.add_option('-p', '--type', default='vm', | 920 parser.add_option('-p', '--type', default='vm', |
771 help='type of test to run: [vm, real]. Default: vm.') | 921 help='type of test to run: [vm, real]. Default: vm.') |
772 parser.add_option('--verbose', default=True, action='store_true', | 922 parser.add_option('--verbose', default=True, action='store_true', |
773 help='Print out rather than capture output as much as ' | 923 help='Print out rather than capture output as much as ' |
774 'possible.') | 924 'possible.') |
775 (options, leftover_args) = parser.parse_args() | 925 (options, leftover_args) = parser.parse_args() |
776 | 926 |
777 if leftover_args: | 927 if leftover_args: |
778 parser.error('Found extra options we do not support: %s' % leftover_args) | 928 parser.error('Found extra options we do not support: %s' % leftover_args) |
779 | 929 |
930 # Figure out the test_class. | |
780 if options.type == 'vm': test_class = VirtualAUTest | 931 if options.type == 'vm': test_class = VirtualAUTest |
781 elif options.type == 'real': test_class = RealAUTest | 932 elif options.type == 'real': test_class = RealAUTest |
782 else: parser.error('Could not parse harness type %s.' % options.type) | 933 else: parser.error('Could not parse harness type %s.' % options.type) |
783 | 934 |
784 # TODO(sosa): Caching doesn't really make sense on non-vm images (yet). | 935 # TODO(sosa): Caching doesn't really make sense on non-vm images (yet). |
785 if options.type == 'vm': | 936 global dev_server_cache |
786 _PregenerateUpdates(parser, options) | 937 if options.type == 'vm' and options.jobs > 1: |
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() | |
787 | 945 |
788 # Run the test suite. | 946 else: |
789 test_class.ProcessOptions(parser, options) | 947 dev_server_cache = None |
790 test_loader = unittest.TestLoader() | 948 test_suite = _PrepareTestSuite(parser, options, test_class) |
791 test_loader.testMethodPrefix = options.test_prefix | 949 test_result = unittest.TextTestRunner(verbosity=2).run(test_suite) |
792 test_suite = test_loader.loadTestsFromTestCase(test_class) | 950 if not test_result.wasSuccessful(): |
793 test_result = unittest.TextTestRunner(verbosity=2).run(test_suite) | 951 Die('Test harness was not successful.') |
794 if not test_result.wasSuccessful(): | |
795 Die('Test harness was not successful.') | |
796 | 952 |
797 | 953 |
798 if __name__ == '__main__': | 954 if __name__ == '__main__': |
799 main() | 955 main() |
OLD | NEW |