Autotest now logs the hwid as a test keyval.

As part of investigating performance test results, it's helpful to have
information about the physical device on which the tests ran.  This CL
adds the "hwid" to the set of test keyvals outputted by an autotest run.
From the hwid, we can look up detailed information about a device.

This CL also adds some docstrings to existing code in order to get
presubmit checks to pass.

BUG=chromium-os:26401
TEST=Verified the hwid appears in the test "keyval" file when running a
sample test on a local device.

Change-Id: I886762900171234bce3897ed74f0a14d69b3a928
Reviewed-on: https://gerrit.chromium.org/gerrit/43078
Reviewed-by: Scott Zawalski <scottz@chromium.org>
Commit-Queue: Dennis Jeffrey <dennisjeffrey@chromium.org>
Tested-by: Dennis Jeffrey <dennisjeffrey@chromium.org>
diff --git a/client/bin/base_sysinfo.py b/client/bin/base_sysinfo.py
index ed834a2..4fb2b3c 100644
--- a/client/bin/base_sysinfo.py
+++ b/client/bin/base_sysinfo.py
@@ -33,6 +33,12 @@
 
 
     def readline(self, logdir):
+        """Reads one line from the log.
+
+        @param logdir: The log directory.
+        @return A line from the log, or the empty string if the log doesn't
+            exist.
+        """
         path = os.path.join(logdir, self.logf)
         if os.path.exists(path):
             return utils.read_one_line(path)
@@ -41,6 +47,7 @@
 
 
 class logfile(loggable):
+    """Represents a log file."""
     def __init__(self, path, logf=None, log_in_keyval=False):
         if not logf:
             logf = os.path.basename(path)
@@ -74,11 +81,16 @@
 
 
     def run(self, logdir):
+        """Copies the log file to the specified directory.
+
+        @param logdir: The log directory.
+        """
         if os.path.exists(self.path):
             shutil.copyfile(self.path, os.path.join(logdir, self.logf))
 
 
 class command(loggable):
+    """Represents a command."""
     def __init__(self, cmd, logf=None, log_in_keyval=False, compress_log=False):
         if not logf:
             logf = cmd.replace(" ", "_")
@@ -113,6 +125,10 @@
 
 
     def run(self, logdir):
+        """Runs the command.
+
+        @param logdir: The log directory.
+        """
         env = os.environ.copy()
         if "PATH" not in env:
             env["PATH"] = "/usr/bin:/bin"
@@ -131,6 +147,7 @@
 
 
 class base_sysinfo(object):
+    """Represents system info."""
     def __init__(self, job_resultsdir):
         self.sysinfodir = self._get_sysinfodir(job_resultsdir)
 
@@ -184,10 +201,16 @@
 
 
     def serialize(self):
+        """Returns parameters in a dictionary."""
         return {"boot": self.boot_loggables, "test": self.test_loggables}
 
 
     def deserialize(self, serialized):
+        """Stores parameters from a dictionary into instance variables.
+
+        @param serialized: A dictionary containing parameters to store as
+            instance variables.
+        """
         self.boot_loggables = serialized["boot"]
         self.test_loggables = serialized["test"]
 
@@ -229,8 +252,7 @@
 
     @log.log_and_ignore_errors("post-reboot sysinfo error:")
     def log_per_reboot_data(self):
-        """ Logging hook called whenever a job starts, and again after
-        any reboot. """
+        """Logging hook called when a job starts, and again after any reboot."""
         logdir = self._get_boot_subdir(next=True)
         if not os.path.exists(logdir):
             os.mkdir(logdir)
@@ -246,7 +268,10 @@
 
     @log.log_and_ignore_errors("pre-test sysinfo error:")
     def log_before_each_test(self, test):
-        """ Logging hook called before a test starts. """
+        """Logging hook called before a test starts.
+
+        @param test: A test object.
+        """
         self._installed_packages = package.list_all()
         if os.path.exists("/var/log/messages"):
             stat = os.stat("/var/log/messages")
@@ -256,7 +281,10 @@
 
     @log.log_and_ignore_errors("post-test sysinfo error:")
     def log_after_each_test(self, test):
-        """ Logging hook called after a test finishs. """
+        """Logging hook called after a test finishs.
+
+        @param test: A test object.
+        """
         test_sysinfodir = self._get_sysinfodir(test.outputdir)
 
         # create a symlink in the test sysinfo dir to the current boot
@@ -295,7 +323,11 @@
 
     @log.log_and_ignore_errors("pre-test siteration sysinfo error:")
     def log_before_each_iteration(self, test, iteration=None):
-        """ Logging hook called before a test iteration."""
+        """Logging hook called before a test iteration.
+
+        @param test: A test object.
+        @param iteration: A test iteration.
+        """
         if not iteration:
             iteration = test.iteration
         logdir = self._get_iteration_subdir(test, iteration)
@@ -306,7 +338,11 @@
 
     @log.log_and_ignore_errors("post-test siteration sysinfo error:")
     def log_after_each_iteration(self, test, iteration=None):
-        """ Logging hook called after a test iteration."""
+        """Logging hook called after a test iteration.
+
+        @param test: A test object.
+        @param iteration: A test iteration.
+        """
         if not iteration:
             iteration = test.iteration
         logdir = self._get_iteration_subdir(test, iteration)
@@ -344,8 +380,12 @@
 
 
     def log_test_keyvals(self, test_sysinfodir):
-        """ Logging hook called by log_after_each_test to collect keyval
-        entries to be written in the test keyval. """
+        """Logging hook called by log_after_each_test.
+
+        Collects keyval entries to be written in the test keyval.
+
+        @param test_sysinfodir: The test's system info directory.
+        """
         keyval = {}
 
         # grab any loggables that should be in the keyval
diff --git a/client/bin/site_sysinfo.py b/client/bin/site_sysinfo.py
index 3bc1244..6266aaa 100755
--- a/client/bin/site_sysinfo.py
+++ b/client/bin/site_sysinfo.py
@@ -2,7 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import os, shutil, re, logging
+import os
 
 from autotest_lib.client.common_lib import utils, global_config
 from autotest_lib.client.bin import base_sysinfo
@@ -18,6 +18,7 @@
 
 
 class logdir(base_sysinfo.loggable):
+    """Represents a log directory."""
     def __init__(self, directory, additional_exclude=None):
         super(logdir, self).__init__(directory, log_in_keyval=False)
         self.dir = directory
@@ -50,6 +51,10 @@
 
 
     def run(self, log_dir):
+        """Copies this log directory to the specified directory.
+
+        @param log_dir: The destination log directory.
+        """
         if os.path.exists(self.dir):
             parent_dir = os.path.dirname(self.dir)
             utils.system("mkdir -p %s%s" % (log_dir, parent_dir))
@@ -65,11 +70,16 @@
 
 
 class purgeable_logdir(logdir):
+    """Represents a log directory that will be purged."""
     def __init__(self, directory, additional_exclude=None):
         super(purgeable_logdir, self).__init__(directory, additional_exclude)
         self.additional_exclude = additional_exclude
 
     def run(self, log_dir):
+        """Copies this log dir to the destination dir, then purges the source.
+
+        @param log_dir: The destination log directory.
+        """
         super(purgeable_logdir, self).run(log_dir)
 
         if os.path.exists(self.dir):
@@ -77,6 +87,7 @@
 
 
 class site_sysinfo(base_sysinfo.base_sysinfo):
+    """Represents site system info."""
     def __init__(self, job_resultsdir):
         super(site_sysinfo, self).__init__(job_resultsdir)
         crash_exclude_string = None
@@ -131,5 +142,8 @@
                     lsb_dict[lsb_key].rstrip(")").split(" ")[3])
             keyval[lsb_key] = lsb_dict[lsb_key]
 
+        # get the hwid (hardware ID)
+        keyval["hwid"] = utils.system_output('crossystem hwid')
+
         # return the updated keyvals
         return keyval