Reland "[Autotest] Migrating client/bin/job.py to python3"

This is a reland of 96cdb58d2e41d7aae5bd276ba2aa48eaba12e791
Fixed the issue which resulted in a revert.

TEST=local testing with custom .py's. Unfortunantly, with this fix, and
with the old ToT (ie, prior to the bad CL landing), it fails when I
forced it to rebuild the deps on "Unhandled KeyError: 'SYSROOT'".
I am guessing this is a var set within the lxc/drone.
However, the custom testing I did showed the issue and that this will
fix it, and be py3 compatible.

Original change's description:
> [Autotest] Migrating client/bin/job.py to python3
>
> This is causing issues. Making the file solo to help debug CQ problems
>
> BUG=chromium:990593
> TEST= py_compile in py2 and py3. Applicable unittests. CQ. dummy_Pass
>
> Change-Id: I90856bc5652965770fc8300423bba6c61fc21548
> Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/autotest/+/2424744
> Reviewed-by: Gregory Nisbet <gregorynisbet@google.com>
> Commit-Queue: Derek Beckett <dbeckett@chromium.org>
> Tested-by: Derek Beckett <dbeckett@chromium.org>
> Auto-Submit: Derek Beckett <dbeckett@chromium.org>

Bug: chromium:990593
Change-Id: I22c0703b55341e08236413d56a704149242cd075
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/autotest/+/2450830
Reviewed-by: Gregory Nisbet <gregorynisbet@google.com>
Reviewed-by: Ilja H. Friedel <ihf@chromium.org>
Commit-Queue: Ilja H. Friedel <ihf@chromium.org>
Tested-by: Ilja H. Friedel <ihf@chromium.org>
diff --git a/client/bin/job.py b/client/bin/job.py
index b634b5e..948db53 100644
--- a/client/bin/job.py
+++ b/client/bin/job.py
@@ -1,3 +1,4 @@
+# Lint as: python2, python3
 """The main job wrapper
 
 This is the core infrastructure.
@@ -21,6 +22,8 @@
 import types
 import weakref
 
+import six
+
 import common
 from autotest_lib.client.bin import client_logging_config
 from autotest_lib.client.bin import harness
@@ -130,7 +133,7 @@
         self._pre_record_init(control, options)
         try:
             self._post_record_init(control, options, drop_caches)
-        except Exception, err:
+        except Exception as err:
             self.record(
                     'ABORT', None, None,'client.bin.job.__init__ failed: %s' %
                     str(err))
@@ -479,10 +482,15 @@
                 raise error.TestError("Dependency %s does not exist" % dep)
 
             os.chdir(dep_dir)
-            if execfile('%s.py' % dep, {}) is None:
+            # Run the dependency, as it could create more files needed for the
+            # tests.
+            # In future this might want to be changed, as this always returns
+            # None, unless the dep.py errors. In which case, it'll error rather
+            # than returning.
+            if eval(compile(open('%s.py' % dep, "rb").read(),
+                            '%s.py' % dep, 'exec'), {}) is None:
                 logging.info('Dependency %s successfuly built', dep)
 
-
     def _runtest(self, url, tag, timeout, args, dargs):
         try:
             l = lambda : test.runtest(self, url, tag, args, dargs)
@@ -495,7 +503,7 @@
             raise
         except error.JobError:
             raise  # Caught further up and turned into an ABORT.
-        except Exception, e:
+        except Exception as e:
             # Converts all other exceptions thrown by the test regardless
             # of phase into a TestError(TestBaseException) subclass that
             # reports them with their full stack trace.
@@ -545,7 +553,7 @@
         def group_func():
             try:
                 self._runtest(url, tag, timeout, args, dargs)
-            except error.TestBaseException, detail:
+            except error.TestBaseException as detail:
                 # The error is already classified, record it properly.
                 self.record(detail.exit_status, subdir, testname, str(detail))
                 raise
@@ -623,7 +631,7 @@
         try:
             self._rungroup(subdir, testname, group_func, timeout)
             return 'GOOD'
-        except error.TestBaseException, detail:
+        except error.TestBaseException as detail:
             return detail.exit_status
 
 
@@ -654,13 +662,13 @@
                 result = function(*args, **dargs)
                 self.record('END GOOD', subdir, testname)
                 return result
-            except error.TestBaseException, e:
+            except error.TestBaseException as e:
                 self.record('END %s' % e.exit_status, subdir, testname)
                 raise
-            except error.JobError, e:
+            except error.JobError as e:
                 self.record('END ABORT', subdir, testname)
                 raise
-            except Exception, e:
+            except Exception as e:
                 # This should only ever happen due to a bug in the given
                 # function's code.  The common case of being called by
                 # run_test() will never reach this.  If a control file called
@@ -697,7 +705,7 @@
             raise
         # If there was a different exception, turn it into a TestError.
         # It will be caught by step_engine or _run_step_fn.
-        except Exception, e:
+        except Exception as e:
             raise error.UnhandledTestError(e)
 
 
@@ -847,7 +855,7 @@
             # wait for the task to finish
             try:
                 self._forkwait(pid, kwargs.get('timeout'))
-            except Exception, e:
+            except Exception as e:
                 logging.info('pid %d completed with error', pid)
                 exceptions.append(e)
             # copy the logs from the subtask into the main log
@@ -879,7 +887,7 @@
         # write out a job HTML report
         try:
             html_report.create_report(self.resultdir)
-        except Exception, e:
+        except Exception as e:
             logging.error("Error writing job HTML report: %s", e)
 
         # We are about to exit 'complete' so clean up the control file.
@@ -946,7 +954,7 @@
         # defined globally can be used as a next step.
         if callable(fn):
             fn = fn.__name__
-        if not isinstance(fn, types.StringTypes):
+        if not isinstance(fn, six.string_types):
             raise StepError("Next steps must be functions or "
                             "strings containing the function name")
         ancestry = copy.copy(self._current_step_ancestry)
@@ -990,9 +998,9 @@
             return local_vars['__ret']
         except SystemExit:
             raise  # Send error.JobContinue and JobComplete on up to runjob.
-        except error.TestNAError, detail:
+        except error.TestNAError as detail:
             self.record(detail.exit_status, None, fn, str(detail))
-        except Exception, detail:
+        except Exception as detail:
             raise error.UnhandledJobError(detail)
 
 
@@ -1062,12 +1070,13 @@
                                'args': self.args}
         exec(JOB_PREAMBLE, global_control_vars, global_control_vars)
         try:
-            execfile(self.control, global_control_vars, global_control_vars)
-        except error.TestNAError, detail:
+            exec(compile(open(self.control, "rb").read(), self.control, 'exec'),
+                 global_control_vars, global_control_vars)
+        except error.TestNAError as detail:
             self.record(detail.exit_status, None, self.control, str(detail))
         except SystemExit:
             raise  # Send error.JobContinue and JobComplete on up to runjob.
-        except Exception, detail:
+        except Exception as detail:
             # Syntax errors or other general Python exceptions coming out of
             # the top level of the control file itself go through here.
             raise error.UnhandledJobError(detail)
@@ -1223,7 +1232,7 @@
     except error.JobComplete:
         sys.exit(1)
 
-    except error.JobError, instance:
+    except error.JobError as instance:
         logging.error("JOB ERROR: " + str(instance))
         if myjob:
             command = None
@@ -1236,7 +1245,7 @@
         else:
             sys.exit(1)
 
-    except Exception, e:
+    except Exception as e:
         # NOTE: job._run_step_fn and job.step_engine will turn things into
         # a JobError for us.  If we get here, its likely an autotest bug.
         msg = str(e) + '\n' + traceback.format_exc()