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

Side by Side Diff: bin/cros_au_test_harness.py

Issue 6277015: Passes cache location to tests and runs the tests in parallel. (Closed) Base URL: http://git.chromium.org/git/crosutils.git@master
Patch Set: Introduced a bug in refactoring Created 9 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | bin/cros_run_vm_update » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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()
OLDNEW
« no previous file with comments | « no previous file | bin/cros_run_vm_update » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698