[Autotest][PY3] Migrating client/common_lib (time_utils_unittest -> end)

Migrating client/common_lib from time_utils_unittest.py to the end of the dir to python3.
time_utils_unittest, utils_unittest passed. Both client/server tests passed.
utils.py is a major backbone of autotest, so this CL should likely be watched
carefully as it merges.

BUG=chromium:990593
TEST= py_compile in py2 and py3. CQ. dummy_Pass. applicable_unittest

Change-Id: If9f1b20e7c280475a9153fccef4c2f51e327019e
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/autotest/+/2460722
Reviewed-by: Gregory Nisbet <gregorynisbet@google.com>
Reviewed-by: Greg Edelston <gredelston@google.com>
Commit-Queue: Derek Beckett <dbeckett@chromium.org>
Tested-by: Derek Beckett <dbeckett@chromium.org>
diff --git a/client/common_lib/cros/dev_server.py b/client/common_lib/cros/dev_server.py
index 843c518..b2dbb46 100644
--- a/client/common_lib/cros/dev_server.py
+++ b/client/common_lib/cros/dev_server.py
@@ -1,26 +1,33 @@
+# Lint as: python2, python3
 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
 from distutils import version
-import cStringIO
-import HTMLParser
-import httplib
 import json
 import logging
 import multiprocessing
 import os
 import re
+import six
+from six.moves import urllib
+import six.moves.html_parser
+import six.moves.http_client
+import six.moves.urllib.parse
 import time
-import urllib2
-import urlparse
 
 from autotest_lib.client.bin import utils as bin_utils
 from autotest_lib.client.common_lib import android_utils
 from autotest_lib.client.common_lib import error
 from autotest_lib.client.common_lib import global_config
+from autotest_lib.client.common_lib import seven
 from autotest_lib.client.common_lib import utils
 from autotest_lib.client.common_lib.cros import retry
+
 # TODO(cmasone): redo this class using requests module; http://crosbug.com/30107
 
 try:
@@ -139,7 +146,7 @@
     pass
 
 
-class MarkupStripper(HTMLParser.HTMLParser):
+class MarkupStripper(six.moves.html_parser.HTMLParser):
     """HTML parser that strips HTML tags, coded characters like &amp;
 
     Works by, basically, not doing anything for any tags, and only recording
@@ -169,7 +176,7 @@
     """
     strip = MarkupStripper()
     try:
-        strip.feed(message.decode('utf_32'))
+        strip.feed(seven.ensure_text(message, 'utf_32'))
     except UnicodeDecodeError:
         strip.feed(message)
     return strip.get_data()
@@ -236,7 +243,7 @@
     @param address: IP address string
     @returns: hostname string, or original input if not found
     """
-    for hostname, addr in _get_hostname_addr_map().iteritems():
+    for hostname, addr in six.iteritems(_get_hostname_addr_map()):
         if addr == address:
             return hostname
     return address
@@ -272,7 +279,7 @@
     def inner_decorator(method):
         label = method.__name__ if hasattr(method, '__name__') else None
         def metrics_wrapper(*args, **kwargs):
-            @retry.retry((urllib2.URLError, error.CmdError,
+            @retry.retry((urllib.error.URLError, error.CmdError,
                           DevServerOverloadException),
                          timeout_min=timeout_min,
                          exception_to_raise=exception_to_raise,
@@ -281,7 +288,7 @@
                 """This wrapper actually catches the HTTPError."""
                 try:
                     return method(*args, **kwargs)
-                except urllib2.HTTPError as e:
+                except urllib.error.HTTPError as e:
                     error_markup = e.read()
                     raise DevServerException(_strip_http_message(error_markup))
 
@@ -317,7 +324,7 @@
     @param url: a Url string
     @return: a hostname string
     """
-    return urlparse.urlparse(url).hostname
+    return six.moves.urllib.parse.urlparse(url).hostname
 
 
 def get_resolved_hostname(url):
@@ -399,7 +406,7 @@
 
         @return A devserver url, e.g., http://127.0.0.10:8080
         """
-        res = urlparse.urlparse(url)
+        res = six.moves.urllib.parse.urlparse(url)
         if res.netloc:
             return res.scheme + '://' + res.netloc
 
@@ -438,7 +445,7 @@
             return cls.run_call(call, timeout=timeout_min*60)
 
         try:
-            return json.load(cStringIO.StringIO(get_load(devserver=devserver)))
+            return json.load(six.StringIO(get_load(devserver=devserver)))
         except Exception as e:
             logging.error('Devserver call failed: "%s", timeout: %s seconds,'
                           ' Error: %s', call, timeout_min * 60, e)
@@ -557,8 +564,10 @@
         archive_url_args = _gs_or_local_archive_url_args(
                 kwargs.pop('archive_url', None))
         kwargs.update(archive_url_args)
-
-        argstr = '&'.join(map(lambda x: "%s=%s" % x, kwargs.iteritems()))
+        if 'is_async' in kwargs:
+            f = kwargs.pop('is_async')
+            kwargs['async'] = f
+        argstr = '&'.join(["%s=%s" % x for x in six.iteritems(kwargs)])
         return "%(host)s/%(method)s?%(argstr)s" % dict(
                 host=host, method=method, argstr=argstr)
 
@@ -609,10 +618,10 @@
             return utils.urlopen_socket_timeout(
                     call, timeout=timeout).read()
         elif readline:
-            response = urllib2.urlopen(call)
+            response = urllib.request.urlopen(call)
             return [line.rstrip() for line in response]
         else:
-            return urllib2.urlopen(call).read()
+            return urllib.request.urlopen(call).read()
 
 
     @staticmethod
@@ -853,8 +862,8 @@
             if request.status_code == requests.codes.OK:
                 return request.text
 
-        error_fd = cStringIO.StringIO(request.text)
-        raise urllib2.HTTPError(
+        error_fd = six.StringIO(request.text)
+        raise urllib.error.HTTPError(
                 call, request.status_code, request.text, request.headers,
                 error_fd)
 
@@ -1060,10 +1069,10 @@
                 result = self.run_call(call)
                 logging.debug('whether artifact is staged: %r', result)
                 return result == 'True'
-            except urllib2.HTTPError as e:
+            except urllib.error.HTTPError as e:
                 error_markup = e.read()
                 raise DevServerException(_strip_http_message(error_markup))
-            except urllib2.URLError as e:
+            except urllib.error.URLError as e:
                 # Could be connection issue, retry it.
                 # For example: <urlopen error [Errno 111] Connection refused>
                 logging.error('URLError happens in is_stage: %r', e)
@@ -1099,7 +1108,7 @@
         @raise DevServerException upon any return code that's expected_response.
 
         """
-        call = self.build_call(call_name, async=True, **kwargs)
+        call = self.build_call(call_name, is_async=True, **kwargs)
         try:
             response = self.run_call(call)
             logging.debug('response for RPC: %r', response)
@@ -1108,7 +1117,7 @@
                               'will retry in 30 seconds')
                 time.sleep(30)
                 raise DevServerOverloadException()
-        except httplib.BadStatusLine as e:
+        except six.moves.http_client.BadStatusLine as e:
             logging.error(e)
             raise DevServerException('Received Bad Status line, Devserver %s '
                                      'might have gone down while handling '
@@ -1320,11 +1329,11 @@
         else:
             build_path = build
             kwargs['build'] = build
-        call = self.build_call('locate_file', async=False, **kwargs)
+        call = self.build_call('locate_file', is_async=False, **kwargs)
         try:
             file_path = self.run_call(call)
             return os.path.join(self.url(), 'static', build_path, file_path)
-        except httplib.BadStatusLine as e:
+        except six.moves.http_client.BadStatusLine as e:
             logging.error(e)
             raise DevServerException('Received Bad Status line, Devserver %s '
                                      'might have gone down while handling '
@@ -1382,7 +1391,7 @@
         build = self.translate(build)
         call = self.build_call('list_suite_controls', build=build,
                                suite_name=suite_name)
-        return json.load(cStringIO.StringIO(self.run_call(call)))
+        return json.load(six.StringIO(self.run_call(call)))
 
 
 class ImageServer(ImageServerBase):
@@ -1542,7 +1551,7 @@
         call = self.build_call('setup_telemetry', archive_url=archive_url)
         try:
             response = self.run_call(call)
-        except httplib.BadStatusLine as e:
+        except six.moves.http_client.BadStatusLine as e:
             logging.error(e)
             raise DevServerException('Received Bad Status line, Devserver %s '
                                      'might have gone down while handling '
diff --git a/client/common_lib/cros/dev_server_unittest.py b/client/common_lib/cros/dev_server_unittest.py
index 2338b68..a923cbd 100755
--- a/client/common_lib/cros/dev_server_unittest.py
+++ b/client/common_lib/cros/dev_server_unittest.py
@@ -6,14 +6,14 @@
 
 """Unit tests for client/common_lib/cros/dev_server.py."""
 
-import httplib
+import six.moves.http_client
 import json
 import mox
 import os
-import StringIO
+import six
+from six.moves import urllib
 import time
 import unittest
-import urllib2
 
 import mock
 
@@ -64,16 +64,16 @@
         self.result_obj = MockSshResponse('error', exit_status=255)
 
 
-E403 = urllib2.HTTPError(url='',
-                         code=httplib.FORBIDDEN,
+E403 = urllib.error.HTTPError(url='',
+                         code=six.moves.http_client.FORBIDDEN,
                          msg='Error 403',
                          hdrs=None,
-                         fp=StringIO.StringIO('Expected.'))
-E500 = urllib2.HTTPError(url='',
-                         code=httplib.INTERNAL_SERVER_ERROR,
+                         fp=six.StringIO('Expected.'))
+E500 = urllib.error.HTTPError(url='',
+                         code=six.moves.http_client.INTERNAL_SERVER_ERROR,
                          msg='Error 500',
                          hdrs=None,
-                         fp=StringIO.StringIO('Expected.'))
+                         fp=six.StringIO('Expected.'))
 CMD_ERROR = error.CmdError('error_cmd', MockSshError().result_obj)
 
 
@@ -88,7 +88,7 @@
         self.contents_readline = ['file/one', 'file/two']
         self.save_ssh_config = dev_server.ENABLE_SSH_CONNECTION_FOR_DEVSERVER
         super(RunCallTest, self).setUp()
-        self.mox.StubOutWithMock(urllib2, 'urlopen')
+        self.mox.StubOutWithMock(urllib.request, 'urlopen')
         self.mox.StubOutWithMock(utils, 'run')
 
         sleep = mock.patch('time.sleep', autospec=True)
@@ -107,11 +107,11 @@
         (call)."""
         dev_server.ENABLE_SSH_CONNECTION_FOR_DEVSERVER = False
 
-        urllib2.urlopen(mox.StrContains(self.test_call)).AndReturn(
-                StringIO.StringIO(dev_server.ERR_MSG_FOR_DOWN_DEVSERVER))
+        urllib.request.urlopen(mox.StrContains(self.test_call)).AndReturn(
+                six.StringIO(dev_server.ERR_MSG_FOR_DOWN_DEVSERVER))
         time.sleep(mox.IgnoreArg())
-        urllib2.urlopen(mox.StrContains(self.test_call)).AndReturn(
-                StringIO.StringIO(self.contents))
+        urllib.request.urlopen(mox.StrContains(self.test_call)).AndReturn(
+                six.StringIO(self.contents))
         self.mox.ReplayAll()
         response = dev_server.ImageServerBase.run_call(self.test_call)
         self.assertEquals(self.contents, response)
@@ -145,8 +145,8 @@
         (call)."""
         dev_server.ENABLE_SSH_CONNECTION_FOR_DEVSERVER = False
 
-        urllib2.urlopen(mox.StrContains(self.test_call)).AndReturn(
-                StringIO.StringIO(self.contents))
+        urllib.request.urlopen(mox.StrContains(self.test_call)).AndReturn(
+                six.StringIO(self.contents))
         self.mox.ReplayAll()
         response = dev_server.ImageServerBase.run_call(self.test_call)
         self.assertEquals(self.contents, response)
@@ -157,8 +157,8 @@
         (call, readline=True)."""
         dev_server.ENABLE_SSH_CONNECTION_FOR_DEVSERVER = False
 
-        urllib2.urlopen(mox.StrContains(self.test_call)).AndReturn(
-                StringIO.StringIO('\n'.join(self.contents_readline)))
+        urllib.request.urlopen(mox.StrContains(self.test_call)).AndReturn(
+                six.StringIO('\n'.join(self.contents_readline)))
         self.mox.ReplayAll()
         response = dev_server.ImageServerBase.run_call(
                 self.test_call, readline=True)
@@ -170,8 +170,8 @@
         (call, timeout=xxx)."""
         dev_server.ENABLE_SSH_CONNECTION_FOR_DEVSERVER = False
 
-        urllib2.urlopen(mox.StrContains(self.test_call), data=None).AndReturn(
-                StringIO.StringIO(self.contents))
+        urllib.request.urlopen(mox.StrContains(self.test_call), data=None).AndReturn(
+                six.StringIO(self.contents))
         self.mox.ReplayAll()
         response = dev_server.ImageServerBase.run_call(
                 self.test_call, timeout=60)
@@ -235,9 +235,9 @@
         """Test dev_server.ImageServerBase.run_call using http with raising
         exception."""
         dev_server.ENABLE_SSH_CONNECTION_FOR_DEVSERVER = False
-        urllib2.urlopen(mox.StrContains(self.test_call)).AndRaise(E500)
+        urllib.request.urlopen(mox.StrContains(self.test_call)).AndRaise(E500)
         self.mox.ReplayAll()
-        self.assertRaises(urllib2.HTTPError,
+        self.assertRaises(urllib.error.HTTPError,
                           dev_server.ImageServerBase.run_call,
                           self.test_call)
 
@@ -262,9 +262,9 @@
     def testRunCallByDevServerHTTP(self):
         """Test dev_server.DevServer.run_call, which uses http, and can be
         directly called by CrashServer."""
-        urllib2.urlopen(
+        urllib.request.urlopen(
                 mox.StrContains(self.test_call), data=None).AndReturn(
-                        StringIO.StringIO(self.contents))
+                        six.StringIO(self.contents))
         self.mox.ReplayAll()
         response = dev_server.DevServer.run_call(
                self.test_call, timeout=60)
@@ -290,7 +290,7 @@
         self.android_dev_server = dev_server.AndroidBuildServer(
                 DevServerTest._HOST)
         self.mox.StubOutWithMock(dev_server.ImageServerBase, 'run_call')
-        self.mox.StubOutWithMock(urllib2, 'urlopen')
+        self.mox.StubOutWithMock(urllib.request, 'urlopen')
         self.mox.StubOutWithMock(utils, 'run')
         self.mox.StubOutWithMock(os.path, 'exists')
         # Hide local restricted_subnets setting.
@@ -360,7 +360,7 @@
         # Mock out bad ping failure to bad_host by raising devserver exception.
         dev_server.ImageServerBase.run_call(
                 argument1, timeout=mox.IgnoreArg()).MultipleTimes().AndRaise(
-                        urllib2.URLError('urlopen connection timeout'))
+                        urllib.error.URLError('urlopen connection timeout'))
 
         # Good host is good.
         dev_server.ImageServerBase.run_call(
@@ -443,7 +443,7 @@
         """Should retry on URLError, but pass through real exception."""
         self.mox.StubOutWithMock(time, 'sleep')
 
-        refused = urllib2.URLError('[Errno 111] Connection refused')
+        refused = urllib.error.URLError('[Errno 111] Connection refused')
         dev_server.ImageServerBase.run_call(
                 mox.IgnoreArg()).AndRaise(refused)
         time.sleep(mox.IgnoreArg())
diff --git a/client/common_lib/seven.py b/client/common_lib/seven.py
index f766ed3..4b6fd65 100644
--- a/client/common_lib/seven.py
+++ b/client/common_lib/seven.py
@@ -11,12 +11,29 @@
 import six
 import six.moves.configparser
 import socket
+import sys
+import types
+
 if six.PY3:
     import builtins
     SOCKET_ERRORS = (builtins.ConnectionError, socket.timeout, socket.gaierror,
                      socket.herror)
+    string_types = (str,)
+    integer_types = (int,)
+    class_types = (type,)
+    text_type = str
+    binary_type = bytes
+
+    MAXSIZE = sys.maxsize
 else:
     SOCKET_ERRORS = (socket.error, )
+    string_types = (basestring,)
+    integer_types = (int, long)
+    class_types = (type, types.ClassType)
+    text_type = unicode
+    binary_type = str
+
+    MAXSIZE = float("inf")
 
 
 def exec_file(filename, globals_, locals_):
@@ -56,3 +73,21 @@
     if six.PY3:
         return six.moves.configparser.ConfigParser(strict=False)
     return six.moves.configparser.ConfigParser()
+
+
+def ensure_text(s, encoding='utf-8', errors='strict'):
+    """Coerce *s* to six.text_type. Copied from six lib.
+
+    For Python 2:
+      - `unicode` -> `unicode`
+      - `str` -> `unicode`
+    For Python 3:
+      - `str` -> `str`
+      - `bytes` -> decoded to `str`
+    """
+    if isinstance(s, binary_type):
+        return s.decode(encoding, errors)
+    elif isinstance(s, text_type):
+        return s
+    else:
+        raise TypeError("not expecting type '%s'" % type(s))
diff --git a/client/common_lib/ui_utils.py b/client/common_lib/ui_utils.py
index ba3a55a..0054a68 100644
--- a/client/common_lib/ui_utils.py
+++ b/client/common_lib/ui_utils.py
@@ -1,6 +1,13 @@
+# Lint as: python2, python3
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
 import logging
 import os
+from six.moves import range
 import time
+
 from autotest_lib.client.common_lib import error
 from autotest_lib.client.common_lib import utils
 
@@ -13,7 +20,7 @@
 
     def __init__(self):
         if not os.path.exists(self._SCREENSHOT_DIR_PATH):
-            os.mkdir(self._SCREENSHOT_DIR_PATH, 0755)
+            os.mkdir(self._SCREENSHOT_DIR_PATH, 0o755)
         self.screenshot_num = 0
 
     def take_ss(self):
@@ -296,7 +303,7 @@
         self.doDefault_on_obj(item_to_click,
                               role=click_role,
                               isRegex=isRegex_click)
-        for retry in xrange(3):
+        for retry in range(3):
             try:
                 self.wait_for_ui_obj(item_to_wait_for,
                                      role=wait_role,
diff --git a/client/common_lib/utils.py b/client/common_lib/utils.py
index 975be78..bcd43ee 100644
--- a/client/common_lib/utils.py
+++ b/client/common_lib/utils.py
@@ -1,3 +1,4 @@
+# Lint as: python2, python3
 # Copyright (c) 2017 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
@@ -11,7 +12,10 @@
 
 # pylint: disable=missing-docstring
 
-import StringIO
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
 import collections
 import datetime
 import errno
@@ -20,7 +24,6 @@
 import logging
 import os
 import pickle
-import Queue
 import random
 import re
 import resource
@@ -28,22 +31,31 @@
 import shutil
 import signal
 import socket
+import six
+from six.moves import input
+from six.moves import range
+from six.moves import urllib
+from six.moves import zip
+from six.moves import zip_longest
+import six.moves.urllib.parse
 import string
 import struct
 import subprocess
 import textwrap
 import threading
 import time
-import urllib2
-import urlparse
+import six.moves.queue
 import uuid
 import warnings
 
 try:
     import hashlib
-except ImportError:
-    import md5
-    import sha
+except ImportError as e:
+    if six.PY2:
+        import md5
+        import sha
+    else:
+        raise ImportError("Broken hashlib imports %s", e)
 
 import common
 
@@ -202,7 +214,7 @@
 
         # allow for easy stdin input by string, we'll let subprocess create
         # a pipe for stdin input and we'll write to it in the wait loop
-        if isinstance(stdin, basestring):
+        if isinstance(stdin, six.string_types):
             self.string_stdin = stdin
             stdin = subprocess.PIPE
         else:
@@ -242,9 +254,9 @@
                 env=env, close_fds=False)
         self._cleanup_called = False
         self._stdout_file = (
-            None if stdout_tee == DEVNULL else StringIO.StringIO())
+            None if stdout_tee == DEVNULL else six.StringIO())
         self._stderr_file = (
-            None if stderr_tee == DEVNULL else StringIO.StringIO())
+            None if stderr_tee == DEVNULL else six.StringIO())
 
     def process_output(self, stdout=True, final_read=False):
         """Read from process's output stream, and write data to destinations.
@@ -395,7 +407,7 @@
     if find != None:
         return re.split("%s" % sep, find.group(1))[param]
     else:
-        print "There is no line which starts with %s in data." % linestart
+        print("There is no line which starts with %s in data." % linestart)
         return None
 
 
@@ -453,7 +465,7 @@
             lengths.append(len(column))
     for row in matrix:
         for i, column in enumerate(row):
-            column = unicode(column).encode("utf-8")
+            column = six.ensure_binary(six.text_type(column), "utf-8")
             cl = len(column)
             try:
                 ml = lengths[i]
@@ -556,7 +568,7 @@
 def is_url(path):
     """Return true if path looks like a URL"""
     # for now, just handle http and ftp
-    url_parts = urlparse.urlparse(path)
+    url_parts = six.moves.urllib.parse.urlparse(path)
     return (url_parts[0] in ('http', 'ftp'))
 
 
@@ -567,7 +579,7 @@
     old_timeout = socket.getdefaulttimeout()
     socket.setdefaulttimeout(timeout)
     try:
-        return urllib2.urlopen(url, data=data)
+        return urllib.request.urlopen(url, data=data)
     finally:
         socket.setdefaulttimeout(old_timeout)
 
@@ -645,7 +657,7 @@
                             (after retrieving it)
     """
     if is_url(src):
-        url_parts = urlparse.urlparse(src)
+        url_parts = six.moves.urllib.parse.urlparse(src)
         filename = os.path.basename(url_parts[2])
         dest = os.path.join(destdir, filename)
         return get_file(src, dest)
@@ -730,7 +742,7 @@
     @raise CmdError: the exit code of the command execution was not 0
     @raise CmdTimeoutError: the command timed out and ignore_timeout is False.
     """
-    if isinstance(args, basestring):
+    if isinstance(args, six.string_types):
         raise TypeError('Got a string for the "args" keyword argument, '
                         'need a sequence.')
 
@@ -738,7 +750,7 @@
     # (For example, see get_user_hash in client/cros/cryptohome.py.)
     # So, to cover that case, detect if it's a string or not and convert it
     # into one if necessary.
-    if not isinstance(command, basestring):
+    if not isinstance(command, six.string_types):
         command = ' '.join([sh_quote_word(arg) for arg in command])
 
     command = ' '.join([command] + [sh_quote_word(arg) for arg in args])
@@ -783,7 +795,7 @@
     bg_jobs = []
     if nicknames is None:
         nicknames = []
-    for (command, nickname) in itertools.izip_longest(commands, nicknames):
+    for (command, nickname) in zip_longest(commands, nicknames):
         bg_jobs.append(BgJob(command, stdout_tee, stderr_tee,
                              stderr_level=get_stderr_level(ignore_status),
                              nickname=nickname))
@@ -1103,7 +1115,7 @@
         for key in input_obj.keys():
             output[str(key)] = strip_unicode(input_obj[key])
         return output
-    elif type(input_obj) == unicode:
+    elif type(input_obj) == six.text_type:
         return str(input_obj)
     else:
         return input_obj
@@ -1164,7 +1176,7 @@
 
     cmd = 'file --brief --dereference /bin/sh'
     filestr = run_function(cmd).stdout.rstrip()
-    for a, regex in archs.iteritems():
+    for a, regex in six.iteritems(archs):
         if re.match(regex, filestr):
             return a
 
@@ -1593,7 +1605,7 @@
     ref_list = reference.split(os.path.sep)[1:]
 
     # find the longest leading common path
-    for i in xrange(min(len(path_list), len(ref_list))):
+    for i in range(min(len(path_list), len(ref_list))):
         if path_list[i] != ref_list[i]:
             # decrement i so when exiting this loop either by no match or by
             # end of range we are one step behind
@@ -1790,7 +1802,7 @@
     if auto:
         logging.info("%s (y/n) y", question)
         return "y"
-    return raw_input("%s INFO | %s (y/n) " %
+    return input("%s INFO | %s (y/n) " %
                      (time.strftime("%H:%M:%S", time.localtime()), question))
 
 
@@ -2103,7 +2115,7 @@
 
     for view in job_views:
         if (view.get('attributes')
-            and constants.CHROME_VERSION in view['attributes'].keys()):
+            and constants.CHROME_VERSION in list(view['attributes'].keys())):
 
             return view['attributes'].get(constants.CHROME_VERSION)
 
@@ -2248,7 +2260,7 @@
         signal_queue = [signal.SIGTERM, signal.SIGKILL]
     sig_count = {}
     # Though this is slightly hacky it beats hardcoding names anyday.
-    sig_names = dict((k, v) for v, k in signal.__dict__.iteritems()
+    sig_names = dict((k, v) for v, k in six.iteritems(signal.__dict__)
                      if v.startswith('SIG'))
     for sig in signal_queue:
         logging.debug('Sending signal %s to the following pids:', sig)
@@ -2312,8 +2324,8 @@
     old_timeout = socket.getdefaulttimeout()
     socket.setdefaulttimeout(timeout)
     try:
-        return urllib2.urlopen(url, data=data)
-    except urllib2.URLError as e:
+        return urllib.request.urlopen(url, data=data)
+    except urllib.error.URLError as e:
         if type(e.reason) is socket.timeout:
             raise error.TimeoutException(str(e))
         raise
@@ -2590,7 +2602,7 @@
     @return: True if the two IP addresses are in the same subnet.
 
     """
-    mask = ((2L<<mask_bits-1) -1)<<(32-mask_bits)
+    mask = ((2<<mask_bits-1) -1)<<(32-mask_bits)
     ip_1_num = struct.unpack('!I', socket.inet_aton(ip_1))[0]
     ip_2_num = struct.unpack('!I', socket.inet_aton(ip_2))[0]
     return ip_1_num & mask == ip_2_num & mask
@@ -2638,7 +2650,7 @@
     if not servers and not server_ip_map:
         raise ValueError('Either `servers` or `server_ip_map` must be given.')
     if not servers:
-        servers = server_ip_map.keys()
+        servers = list(server_ip_map.keys())
     # Make sure server_ip_map is an empty dict if it's not set.
     if not server_ip_map:
         server_ip_map = {}
@@ -3068,7 +3080,7 @@
         Creates the queue and starts the thread, then assigns extra attributes
         to the thread to give it result-storing capability.
         """
-        q = Queue.Queue()
+        q = six.moves.queue.Queue()
         t = threading.Thread(target=wrapped_t, args=(q,) + args, kwargs=kwargs)
         t.start()
         t.result_queue = q
@@ -3198,14 +3210,20 @@
       A tuple of: (args tuple, keyword arguments dictionary)
     """
     # Cherry pick args:
-    if func.func_code.co_flags & 0x04:
+    if hasattr(func, "func_code"):
+        # Moock doesn't have __code__ in either py2 or 3 :(
+        flags = func.func_code.co_flags
+    else:
+        flags = func.__code__.co_flags
+
+    if flags & 0x04:
         # func accepts *args, so return the entire args.
         p_args = args
     else:
         p_args = ()
 
     # Cherry pick dargs:
-    if func.func_code.co_flags & 0x08:
+    if flags & 0x08:
         # func accepts **dargs, so return the entire dargs.
         p_dargs = dargs
     else:
@@ -3235,7 +3253,7 @@
 
     @return: A tuple of parameters accepted by the function.
     """
-    return func.func_code.co_varnames[:func.func_code.co_argcount]
+    return func.__code__.co_varnames[:func.__code__.co_argcount]
 
 def crc8(buf):
     """Calculate CRC8 for a given int list.
diff --git a/client/common_lib/utils_unittest.py b/client/common_lib/utils_unittest.py
index 4d71d69..10571dc 100755
--- a/client/common_lib/utils_unittest.py
+++ b/client/common_lib/utils_unittest.py
@@ -2,7 +2,12 @@
 
 # pylint: disable=missing-docstring
 
-import StringIO
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+from six.moves import range
+import six
 import errno
 import itertools
 import logging
@@ -12,7 +17,7 @@
 import subprocess
 import time
 import unittest
-import urllib2
+from six.moves import urllib
 
 import common
 from autotest_lib.client.common_lib import autotemp
@@ -69,7 +74,7 @@
 
 
     def create_test_file(self, contents):
-        test_file = StringIO.StringIO(contents)
+        test_file = six.StringIO(contents)
         utils.open.expect_call("filename", "r").and_return(test_file)
 
 
@@ -180,7 +185,7 @@
 
 
     def create_test_file(self, filename, contents):
-        test_file = StringIO.StringIO(contents)
+        test_file = six.StringIO(contents)
         os.path.exists.expect_call(filename).and_return(True)
         utils.open.expect_call(filename).and_return(test_file)
 
@@ -290,7 +295,7 @@
                      type_tag=None):
         if expected_filename is None:
             expected_filename = filename
-        test_file = StringIO.StringIO()
+        test_file = six.StringIO()
         self.god.stub_function(test_file, "close")
         utils.open.expect_call(expected_filename, "a").and_return(test_file)
         test_file.close.expect_call()
@@ -393,7 +398,7 @@
             self.assertEquals(expected_args, (url,data))
             test_func(socket.getdefaulttimeout())
             return expected_return
-        self.god.stub_with(urllib2, "urlopen", urlopen)
+        self.god.stub_with(urllib.request, "urlopen", urlopen)
 
 
     def stub_urlopen_with_timeout_check(self, expected_timeout,
@@ -515,35 +520,43 @@
 
 
     def test_file_only_at_src(self):
-        print >> open(self.src("src_only"), "w"), "line 1"
+        with open(self.src("src_only"), "w") as wf:
+            print("line 1", file=wf)
         utils.merge_trees(*self.paths("src_only"))
         self.assertFileEqual("src_only")
 
 
     def test_file_only_at_dest(self):
-        print >> open(self.dest("dest_only"), "w"), "line 1"
+        with open(self.dest("dest_only"), "w") as wf:
+            print("line 1", file=wf)
         utils.merge_trees(*self.paths("dest_only"))
         self.assertEqual(False, os.path.exists(self.src("dest_only")))
         self.assertFileContents("line 1\n", "dest_only")
 
 
     def test_file_at_both(self):
-        print >> open(self.dest("in_both"), "w"), "line 1"
-        print >> open(self.src("in_both"), "w"), "line 2"
+        with open(self.dest("in_both"), "w") as wf1:
+            print("line 1", file=wf1)
+        with open(self.src("in_both"), "w") as wf2:
+            print("line 2", file=wf2)
         utils.merge_trees(*self.paths("in_both"))
         self.assertFileContents("line 1\nline 2\n", "in_both")
 
 
     def test_directory_with_files_in_both(self):
-        print >> open(self.dest("in_both"), "w"), "line 1"
-        print >> open(self.src("in_both"), "w"), "line 3"
+        with open(self.dest("in_both"), "w") as wf1:
+            print("line 1", file=wf1)
+        with open(self.src("in_both"), "w") as wf2:
+            print("line 3", file=wf2)
         utils.merge_trees(*self.paths())
         self.assertFileContents("line 1\nline 3\n", "in_both")
 
 
     def test_directory_with_mix_of_files(self):
-        print >> open(self.dest("in_dest"), "w"), "dest line"
-        print >> open(self.src("in_src"), "w"), "src line"
+        with open(self.dest("in_dest"), "w") as wf1:
+            print("dest line", file=wf1)
+        with open(self.src("in_src"), "w") as wf2:
+            print("src line", file=wf2)
         utils.merge_trees(*self.paths())
         self.assertFileContents("dest line\n", "in_dest")
         self.assertFileContents("src line\n", "in_src")
@@ -551,11 +564,14 @@
 
     def test_directory_with_subdirectories(self):
         os.mkdir(self.src("src_subdir"))
-        print >> open(self.src("src_subdir", "subfile"), "w"), "subdir line"
+        with open(self.src("src_subdir", "subfile"), "w") as wf1:
+            print("subdir line", file=wf1)
         os.mkdir(self.src("both_subdir"))
         os.mkdir(self.dest("both_subdir"))
-        print >> open(self.src("both_subdir", "subfile"), "w"), "src line"
-        print >> open(self.dest("both_subdir", "subfile"), "w"), "dest line"
+        with open(self.src("both_subdir", "subfile"), "w") as wf2:
+            print("src line", file=wf2)
+        with open(self.dest("both_subdir", "subfile"), "w") as wf3:
+            print("dest line", file=wf3)
         utils.merge_trees(*self.paths())
         self.assertFileContents("subdir line\n", "src_subdir", "subfile")
         self.assertFileContents("dest line\nsrc line\n", "both_subdir",
@@ -709,7 +725,7 @@
         # Log level -> StringIO.StringIO.
         self.logs = {}
         for level in self.LOG_LEVELS:
-            self.logs[level] = StringIO.StringIO()
+            self.logs[level] = six.StringIO()
 
         # Override logging_manager.LoggingFile to return buffers.
         def logging_file(level=None, prefix=None):
@@ -748,7 +764,7 @@
         cmd = 'exit 11'
         try:
             utils.run(cmd, verbose=False)
-        except utils.error.CmdError, err:
+        except utils.error.CmdError as err:
             self.__check_result(err.result_obj, cmd, exit_status=11)
 
 
@@ -763,14 +779,14 @@
         utils.logging.warning.expect_any_call()
         try:
             utils.run('echo -n output && sleep 10', timeout=1, verbose=False)
-        except utils.error.CmdError, err:
+        except utils.error.CmdError as err:
             self.assertEquals(err.result_obj.stdout, 'output')
 
 
     def test_stdout_stderr_tee(self):
         cmd = 'echo output && echo error >&2'
-        stdout_tee = StringIO.StringIO()
-        stderr_tee = StringIO.StringIO()
+        stdout_tee = six.StringIO()
+        stderr_tee = six.StringIO()
 
         self.__check_result(utils.run(
                 cmd, stdout_tee=stdout_tee, stderr_tee=stderr_tee,
@@ -925,7 +941,7 @@
 
 
     def test_get_port(self):
-        for _ in xrange(100):
+        for _ in range(100):
             p = utils.get_unused_port()
             s = self.do_bind(p, socket.SOCK_STREAM, socket.IPPROTO_TCP)
             self.assert_(s.getsockname())