Merge remote branch 'cros/upstream' into master

Merged to upstream autotest @4749~@5215.

The entire change list description is too big to enlist here. Please refer to upstream (http://autotest.kernel.org/browser) for more details.

BUG=
TEST=emerged both x86 and arm build.
Tested emerged x86 build bvt against a chromeos device.

Review URL: http://codereview.chromium.org/6246035

Change-Id: I8455f2135c87c321c6efc232e2869dc8f675395e
diff --git a/utils/check_patch.py b/utils/check_patch.py
index 576b97d..78af6b9 100755
--- a/utils/check_patch.py
+++ b/utils/check_patch.py
@@ -20,7 +20,7 @@
 @author: Lucas Meneghel Rodrigues <[email protected]>
 """
 
-import os, stat, logging, sys, optparse
+import os, stat, logging, sys, optparse, time
 import common
 from autotest_lib.client.common_lib import utils, error, logging_config
 from autotest_lib.client.common_lib import logging_manager
@@ -32,6 +32,20 @@
                                                                verbose=verbose)
 
 
+def ask(question, auto=False):
+    """
+    Raw input with a prompt that emulates logging.
+
+    @param question: Question to be asked
+    @param auto: Whether to return "y" instead of asking the question
+    """
+    if auto:
+        logging.info("%s (y/n) y" % question)
+        return "y"
+    return raw_input("%s INFO | %s (y/n) " %
+                     (time.strftime("%H:%M:%S", time.localtime()), question))
+
+
 class VCS(object):
     """
     Abstraction layer to the version control system.
@@ -104,6 +118,7 @@
     """
     def __init__(self):
         logging.debug("Subversion VCS backend initialized.")
+        self.ignored_extension_list = ['.orig', '.bak']
 
 
     def get_unknown_files(self):
@@ -112,7 +127,9 @@
         for line in status.split("\n"):
             status_flag = line[0]
             if line and status_flag == "?":
-                unknown_files.append(line[1:].strip())
+                for extension in self.ignored_extension_list:
+                    if not line.endswith(extension):
+                        unknown_files.append(line[1:].strip())
         return unknown_files
 
 
@@ -181,13 +198,16 @@
     Picks up a given file and performs various checks, looking after problems
     and eventually suggesting solutions.
     """
-    def __init__(self, path):
+    def __init__(self, path, confirm=False):
         """
         Class constructor, sets the path attribute.
 
         @param path: Path to the file that will be checked.
+        @param confirm: Whether to answer yes to all questions asked without
+                prompting the user.
         """
         self.path = path
+        self.confirm = confirm
         self.basename = os.path.basename(self.path)
         if self.basename.endswith('.py'):
             self.is_python = True
@@ -204,7 +224,7 @@
         self.first_line = checked_file.readline()
         checked_file.close()
         self.corrective_actions = []
-        self.indentation_exceptions = ['cli/job_unittest.py']
+        self.indentation_exceptions = ['job_unittest.py']
 
 
     def _check_indent(self):
@@ -226,8 +246,6 @@
         reindent_results = reindent_raw.split(" ")[-1].strip(".")
         if reindent_results == "changed":
             if self.basename not in self.indentation_exceptions:
-                logging.error("Possible indentation and spacing issues on "
-                              "file %s" % self.path)
                 self.corrective_actions.append("reindent.py -v %s" % self.path)
 
 
@@ -242,8 +260,7 @@
         c_cmd = 'run_pylint.py %s' % self.path
         rc = utils.system(c_cmd, ignore_status=True)
         if rc != 0:
-            logging.error("Possible syntax problems on file %s", self.path)
-            logging.error("You might want to rerun '%s'", c_cmd)
+            logging.error("Syntax issues found during '%s'", c_cmd)
 
 
     def _check_unittest(self):
@@ -260,9 +277,8 @@
                 unittest_cmd = 'python %s' % unittest_path
                 rc = utils.system(unittest_cmd, ignore_status=True)
                 if rc != 0:
-                    logging.error("Problems during unit test execution "
-                                  "for file %s", self.path)
-                    logging.error("You might want to rerun '%s'", unittest_cmd)
+                    logging.error("Unittest issues found during '%s'",
+                                  unittest_cmd)
 
 
     def _check_permissions(self):
@@ -273,14 +289,10 @@
         """
         if self.first_line.startswith("#!"):
             if not self.is_executable:
-                logging.info("File %s seems to require execution "
-                             "permissions. ", self.path)
-                self.corrective_actions.append("chmod +x %s" % self.path)
+                self.corrective_actions.append("svn propset svn:executable ON %s" % self.path)
         else:
             if self.is_executable:
-                logging.info("File %s does not seem to require execution "
-                             "permissions. ", self.path)
-                self.corrective_actions.append("chmod -x %s" % self.path)
+                self.corrective_actions.append("svn propdel svn:executable %s" % self.path)
 
 
     def report(self):
@@ -294,10 +306,9 @@
             self._check_code()
             self._check_unittest()
         if self.corrective_actions:
-            logging.info("The following corrective actions are suggested:")
             for action in self.corrective_actions:
-                logging.info(action)
-                answer = raw_input("Would you like to apply it? (y/n) ")
+                answer = ask("Would you like to execute %s?" % action,
+                             auto=self.confirm)
                 if answer == "y":
                     rc = utils.system(action, ignore_status=True)
                     if rc != 0:
@@ -305,7 +316,8 @@
 
 
 class PatchChecker(object):
-    def __init__(self, patch=None, patchwork_id=None):
+    def __init__(self, patch=None, patchwork_id=None, confirm=False):
+        self.confirm = confirm
         self.base_dir = os.getcwd()
         if patch:
             self.patch = os.path.abspath(patch)
@@ -322,7 +334,7 @@
         if changed_files_before:
             logging.error("Repository has changed files prior to patch "
                           "application. ")
-            answer = raw_input("Would you like to revert them? (y/n) ")
+            answer = ask("Would you like to revert them?", auto=self.confirm)
             if answer == "n":
                 logging.error("Not safe to proceed without reverting files.")
                 sys.exit(1)
@@ -370,20 +382,20 @@
             for untracked_file in add_to_vcs:
                 logging.info(untracked_file)
             logging.info("Might need to be added to VCS")
-            logging.info("Would you like to add them to VCS ? (y/n/abort) ")
-            answer = raw_input()
+            answer = ask("Would you like to add them to VCS ?")
             if answer == "y":
                 for untracked_file in add_to_vcs:
                     self.vcs.add_untracked_file(untracked_file)
                     modified_files_after.append(untracked_file)
             elif answer == "n":
                 pass
-            elif answer == "abort":
-                sys.exit(1)
 
         for modified_file in modified_files_after:
-            file_checker = FileChecker(modified_file)
-            file_checker.report()
+            # Additional safety check, new commits might introduce
+            # new directories
+            if os.path.isfile(modified_file):
+                file_checker = FileChecker(modified_file)
+                file_checker.report()
 
 
     def check(self):
@@ -399,20 +411,37 @@
                       help='id of a given patchwork patch')
     parser.add_option('--verbose', dest="debug", action='store_true',
                       help='include debug messages in console output')
+    parser.add_option('-f', '--full-check', dest="full_check",
+                      action='store_true',
+                      help='check the full tree for corrective actions')
+    parser.add_option('-y', '--yes', dest="confirm",
+                      action='store_true',
+                      help='Answer yes to all questions')
 
     options, args = parser.parse_args()
     local_patch = options.local_patch
     id = options.id
     debug = options.debug
+    full_check = options.full_check
+    confirm = options.confirm
 
     logging_manager.configure_logging(CheckPatchLoggingConfig(), verbose=debug)
 
-    if local_patch:
-        patch_checker = PatchChecker(patch=local_patch)
-    elif id:
-        patch_checker = PatchChecker(patchwork_id=id)
+    ignore_file_list = ['common.py']
+    if full_check:
+        for root, dirs, files in os.walk('.'):
+            if not '.svn' in root:
+                for file in files:
+                    if file not in ignore_file_list:
+                        path = os.path.join(root, file)
+                        file_checker = FileChecker(path, confirm=confirm)
+                        file_checker.report()
     else:
-        logging.error('No patch or patchwork id specified. Aborting.')
-        sys.exit(1)
-
-    patch_checker.check()
+        if local_patch:
+            patch_checker = PatchChecker(patch=local_patch, confirm=confirm)
+        elif id:
+            patch_checker = PatchChecker(patchwork_id=id, confirm=confirm)
+        else:
+            logging.error('No patch or patchwork id specified. Aborting.')
+            sys.exit(1)
+        patch_checker.check()
diff --git a/utils/external_packages.py b/utils/external_packages.py
old mode 100644
new mode 100755
index d505eef..bcfd15d
--- a/utils/external_packages.py
+++ b/utils/external_packages.py
@@ -613,7 +613,8 @@
 class ParamikoPackage(ExternalPackage):
     version = '1.7.5'
     local_filename = 'paramiko-%s.tar.gz' % version
-    urls = ('http://www.lag.net/paramiko/download/' + local_filename,)
+    urls = ('http://www.lag.net/paramiko/download/' + local_filename,
+            'ftp://mirrors.kernel.org/gentoo/distfiles/' + local_filename,)
     hex_sum = '592be7a08290070b71da63a8e6f28a803399e5c5'
 
 
diff --git a/utils/run_pylint.py b/utils/run_pylint.py
index 0bf5f95..3c3e225 100755
--- a/utils/run_pylint.py
+++ b/utils/run_pylint.py
@@ -6,10 +6,13 @@
 # do a basic check to see if pylint is even installed
 try:
     import pylint
+    from pylint.__pkginfo__ import version as pylint_version
 except ImportError:
     print "Unable to import pylint, it may need to be installed"
     sys.exit(1)
 
+major, minor, release = pylint_version.split('.')
+pylint_version = float("%s.%s" % (major, minor))
 pylintrc_path = os.path.expanduser('~/.pylintrc')
 if not os.path.exists(pylintrc_path):
     open(pylintrc_path, 'w').close()
@@ -54,10 +57,13 @@
 # * common_lib.enum.Enum objects
 # * DB model objects (scheduler models are the worst, but Django models also
 #   generate some errors)
-pylint_base_opts = ['--disable-msg-cat=warning,refactor,convention',
-                    '--disable-msg=E1101,E1103',
-                    '--reports=no',
-                    '--include-ids=y']
+if pylint_version >= 0.21:
+    pylint_base_opts = ['--disable=W,R,C,E1101,E1103']
+else:
+    pylint_base_opts = ['--disable-msg-cat=warning,refactor,convention',
+                        '--disable-msg=E1101,E1103']
+pylint_base_opts += ['--reports=no',
+                     '--include-ids=y']
 
 file_list = sys.argv[1:]
 if '--' in file_list:
diff --git a/utils/test_importer.py b/utils/test_importer.py
index 00b912c..a7be755 100755
--- a/utils/test_importer.py
+++ b/utils/test_importer.py
@@ -242,6 +242,12 @@
         _log_or_execute(repr(new_test), new_test.save)
         add_label_dependencies(new_test)
 
+        # save TestParameter
+        for para_name in data.test_parameters:
+            test_parameter = models.TestParameter.objects.get_or_create(
+                test=new_test, name=para_name)[0]
+            test_parameter.save()
+
 
 def _set_attributes_clean(test, data):
     """
diff --git a/utils/unittest_suite.py b/utils/unittest_suite.py
index 629eba6..78a9602 100755
--- a/utils/unittest_suite.py
+++ b/utils/unittest_suite.py
@@ -16,6 +16,8 @@
 parser.add_option("--skip-tests", dest="skip_tests",  default=[],
                   help="A space separated list of tests to skip")
 
+parser.set_defaults(module_list=None)
+
 
 REQUIRES_DJANGO = set((
         'monitor_db_unittest.py',
@@ -64,6 +66,11 @@
     'logging_manager_test.py',
     ))
 
+# This particular KVM autotest test is not a unittest
+SKIP = set((
+    'guest_test.py',
+    ))
+
 LONG_TESTS = (REQUIRES_DJANGO |
               REQUIRES_MYSQLDB |
               REQUIRES_GWT |
@@ -103,16 +110,10 @@
             raise TestFailure(msg)
 
 
-def find_and_run_tests(start, options):
-    """
-    Find and run Python unittest suites below the given directory.  Only look
-    in subdirectories of start that are actual importable Python modules.
-
-    @param start: The absolute directory to look for tests under.
-    @param options: optparse options.
-    """
+def scan_for_modules(start, options):
     modules = []
-    skip_tests = set()
+
+    skip_tests = SKIP
     if options.skip_tests:
         skip_tests.update(options.skip_tests.split())
 
@@ -142,6 +143,22 @@
                 modules.append(['autotest_lib'] + names)
                 if options.debug:
                     print 'testing', path_no_py
+    return modules
+
+def find_and_run_tests(start, options):
+    """
+    Find and run Python unittest suites below the given directory.  Only look
+    in subdirectories of start that are actual importable Python modules.
+
+    @param start: The absolute directory to look for tests under.
+    @param options: optparse options.
+    """
+    if options.module_list:
+        modules = []
+        for m in options.module_list:
+            modules.append(m.split('.'))
+    else:
+        modules = scan_for_modules(start, options)
 
     if options.debug:
         print 'Number of test modules found:', len(modules)
@@ -170,9 +187,7 @@
 def main():
     options, args = parser.parse_args()
     if args:
-        parser.error('Unexpected argument(s): %s' % args)
-        parser.print_help()
-        sys.exit(1)
+        options.module_list = args
 
     # Strip the arguments off the command line, so that the unit tests do not
     # see them.