Make test_env.py compatible w/ Python 3

Makes test_env.py compatible with both Python 2 and 3. Also adds a very
simple test to ensure that it remains compatible until some other test
switches to Python 3.

As a side effect of adding the test, also makes
testing/scripts/common.py compatible with Python 3 since it's
necessary for running isolated scripts.

Bug: 898348
Change-Id: Ib58cf4c4f85df60a432764d7f744bc3fee09ae3a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1796525
Reviewed-by: Dirk Pranke <dpranke@chromium.org>
Commit-Queue: Brian Sheedy <bsheedy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#695825}
diff --git a/testing/BUILD.gn b/testing/BUILD.gn
index a5ccdd3..e7e5b52 100644
--- a/testing/BUILD.gn
+++ b/testing/BUILD.gn
@@ -39,3 +39,15 @@
     ":test_scripts_shared",
   ]
 }
+
+group("python3_smoketest") {
+  data = [
+    "//testing/python3_smoketest.py",
+    "//testing/scripts/common.py",
+    "//testing/scripts/run_isolated_script_test.py",
+  ]
+
+  data_deps = [
+    ":test_scripts_shared",
+  ]
+}
diff --git a/testing/buildbot/chromium.clang.json b/testing/buildbot/chromium.clang.json
index ec4ab2f72..5835b60 100644
--- a/testing/buildbot/chromium.clang.json
+++ b/testing/buildbot/chromium.clang.json
@@ -21642,6 +21642,17 @@
         }
       },
       {
+        "isolate_name": "python3_smoketest",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "python3_smoketest",
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        }
+      },
+      {
         "isolate_name": "telemetry_gpu_unittests",
         "merge": {
           "args": [],
@@ -23723,6 +23734,17 @@
         }
       },
       {
+        "isolate_name": "python3_smoketest",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "python3_smoketest",
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        }
+      },
+      {
         "isolate_name": "telemetry_gpu_unittests",
         "merge": {
           "args": [],
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 9be031ce..7eb1297b 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -3994,6 +3994,23 @@
         }
       },
       {
+        "isolate_name": "python3_smoketest",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "python3_smoketest",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-17134"
+            }
+          ]
+        }
+      },
+      {
         "isolate_name": "telemetry_gpu_unittests",
         "merge": {
           "args": [],
@@ -15702,6 +15719,22 @@
         }
       },
       {
+        "isolate_name": "python3_smoketest",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "python3_smoketest",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-16.04"
+            }
+          ]
+        }
+      },
+      {
         "args": [
           "--num-retries=3",
           "--additional-driver-flag=--enable-gpu-rasterization",
@@ -23263,6 +23296,24 @@
         }
       },
       {
+        "isolate_name": "python3_smoketest",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "python3_smoketest",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "8086:0a2e",
+              "os": "Mac-10.15"
+            }
+          ],
+          "expiration": 21600
+        }
+      },
+      {
         "isolate_name": "telemetry_gpu_unittests",
         "merge": {
           "args": [],
diff --git a/testing/buildbot/chromium.linux.json b/testing/buildbot/chromium.linux.json
index b793bb1..61e1778 100644
--- a/testing/buildbot/chromium.linux.json
+++ b/testing/buildbot/chromium.linux.json
@@ -3908,6 +3908,23 @@
         }
       },
       {
+        "isolate_coverage_data": true,
+        "isolate_name": "python3_smoketest",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "python3_smoketest",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-16.04"
+            }
+          ]
+        }
+      },
+      {
         "args": [
           "--num-retries=3",
           "--additional-driver-flag=--enable-gpu-rasterization",
@@ -5632,6 +5649,22 @@
         }
       },
       {
+        "isolate_name": "python3_smoketest",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "python3_smoketest",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-16.04"
+            }
+          ]
+        }
+      },
+      {
         "isolate_name": "telemetry_gpu_unittests",
         "merge": {
           "args": [],
@@ -7464,6 +7497,22 @@
         }
       },
       {
+        "isolate_name": "python3_smoketest",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "python3_smoketest",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "os": "Ubuntu-14.04"
+            }
+          ]
+        }
+      },
+      {
         "args": [
           "--num-retries=3",
           "--additional-driver-flag=--enable-gpu-rasterization",
diff --git a/testing/buildbot/chromium.mac.json b/testing/buildbot/chromium.mac.json
index f57b6b2..63e5f55 100644
--- a/testing/buildbot/chromium.mac.json
+++ b/testing/buildbot/chromium.mac.json
@@ -1499,6 +1499,23 @@
         }
       },
       {
+        "isolate_name": "python3_smoketest",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "python3_smoketest",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "none",
+              "os": "Mac-10.10"
+            }
+          ]
+        }
+      },
+      {
         "isolate_name": "telemetry_gpu_unittests",
         "merge": {
           "args": [],
@@ -3052,6 +3069,23 @@
         }
       },
       {
+        "isolate_name": "python3_smoketest",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "python3_smoketest",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "none",
+              "os": "Mac-10.11"
+            }
+          ]
+        }
+      },
+      {
         "isolate_name": "telemetry_gpu_unittests",
         "merge": {
           "args": [],
@@ -4605,6 +4639,23 @@
         }
       },
       {
+        "isolate_name": "python3_smoketest",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "python3_smoketest",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "8086:0a2e",
+              "os": "Mac-10.12.6"
+            }
+          ]
+        }
+      },
+      {
         "isolate_name": "telemetry_gpu_unittests",
         "merge": {
           "args": [],
@@ -6203,6 +6254,23 @@
         }
       },
       {
+        "isolate_name": "python3_smoketest",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "python3_smoketest",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "none",
+              "os": "Mac-10.13.6"
+            }
+          ]
+        }
+      },
+      {
         "isolate_name": "telemetry_gpu_unittests",
         "merge": {
           "args": [],
@@ -7741,6 +7809,23 @@
         }
       },
       {
+        "isolate_name": "python3_smoketest",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "python3_smoketest",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "none",
+              "os": "Mac-10.13.6"
+            }
+          ]
+        }
+      },
+      {
         "isolate_name": "telemetry_gpu_unittests",
         "merge": {
           "args": [],
@@ -9348,6 +9433,23 @@
         }
       },
       {
+        "isolate_name": "python3_smoketest",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "python3_smoketest",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "none",
+              "os": "Mac-10.13.6"
+            }
+          ]
+        }
+      },
+      {
         "isolate_name": "telemetry_gpu_unittests",
         "merge": {
           "args": [],
diff --git a/testing/buildbot/chromium.win.json b/testing/buildbot/chromium.win.json
index 254e09f..f57a1eeb 100644
--- a/testing/buildbot/chromium.win.json
+++ b/testing/buildbot/chromium.win.json
@@ -1183,6 +1183,17 @@
         }
       },
       {
+        "isolate_name": "python3_smoketest",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "python3_smoketest",
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        }
+      },
+      {
         "isolate_name": "telemetry_gpu_unittests",
         "merge": {
           "args": [],
@@ -3060,6 +3071,23 @@
         }
       },
       {
+        "isolate_name": "python3_smoketest",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "python3_smoketest",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
+        }
+      },
+      {
         "isolate_name": "telemetry_gpu_unittests",
         "merge": {
           "args": [],
@@ -4901,6 +4929,23 @@
         }
       },
       {
+        "isolate_name": "python3_smoketest",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "python3_smoketest",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "os": "Windows-10-15063"
+            }
+          ]
+        }
+      },
+      {
         "isolate_name": "telemetry_gpu_unittests",
         "merge": {
           "args": [],
@@ -6207,6 +6252,17 @@
         }
       },
       {
+        "isolate_name": "python3_smoketest",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "python3_smoketest",
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        }
+      },
+      {
         "isolate_name": "telemetry_gpu_unittests",
         "merge": {
           "args": [],
@@ -7408,6 +7464,17 @@
         }
       },
       {
+        "isolate_name": "python3_smoketest",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "python3_smoketest",
+        "swarming": {
+          "can_use_on_swarming_builders": true
+        }
+      },
+      {
         "isolate_name": "telemetry_gpu_unittests",
         "merge": {
           "args": [],
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index ff7e949..afed2b5 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -2288,6 +2288,15 @@
     "label": "//third_party/webrtc/test/fuzzers:pseudotcp_parser_fuzzer",
     "type": "fuzzer",
   },
+  "python3_smoketest": {
+    "args": [
+      "../../testing/python3_smoketest.py",
+    ],
+    "label": "//testing:python3_smoketest",
+    "type": "script",
+    "script": "//testing/scripts/run_isolated_script_test.py",
+    "use_python3": True,
+  },
   "qcms_color_space_fuzzer": {
     "label": "//third_party/qcms:qcms_color_space_fuzzer",
     "type": "fuzzer",
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 9765168e..e5c0d67 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -2732,6 +2732,7 @@
       'flatbuffers_unittests': {},
       'grit_python_unittests': {},
       'metrics_python_tests': {},
+      'python3_smoketest': {},
       'telemetry_gpu_unittests': {
         'swarming': {
           'idempotent': False,  # https://crbug.com/549140
diff --git a/testing/python3_smoketest.py b/testing/python3_smoketest.py
new file mode 100755
index 0000000..918c4b8
--- /dev/null
+++ b/testing/python3_smoketest.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+# Copyright 2019 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.
+
+import argparse
+import json
+import sys
+import time
+
+# Simple test to make sure that Python 3 can be used on swarming and that
+# test_env.py remains compatible with Python 3. Can be removed once another
+# test is moved over to Python 3.
+
+results = {
+  'tests': {
+    'python3_smoketest': {
+      'expected': 'PASS',
+    },
+  },
+  'interrupted': False,
+  'path_delimiter': '.',
+  'version': 3,
+  'seconds_since_epoch': time.time(),
+  'num_failures_by_type': {},
+}
+
+parser = argparse.ArgumentParser()
+parser.add_argument('--write-full-results-to', required=True)
+args = parser.parse_args()
+
+rc = 0
+if sys.version_info[0] == 3:
+  results['tests']['python3_smoketest']['actual'] = 'PASS'
+  results['num_failures_by_type']['PASS'] = 1
+else:
+  rc = 1
+  results['tests']['python3_smoketest']['actual'] = 'FAIL'
+  results['num_failures_by_type']['FAIL'] = 1
+
+with open(args.write_full_results_to, 'w') as f:
+  json.dump(results, f)
+
+sys.exit(rc)
diff --git a/testing/scripts/common.py b/testing/scripts/common.py
index 0fee460..ccebfb8 100644
--- a/testing/scripts/common.py
+++ b/testing/scripts/common.py
@@ -79,9 +79,9 @@
 
 
 def run_command(argv, env=None, cwd=None):
-  print 'Running %r in %r (env: %r)' % (argv, cwd, env)
+  print('Running %r in %r (env: %r)' % (argv, cwd, env))
   rc = test_env.run_command(argv, env=env, cwd=cwd)
-  print 'Command %r returned exit code %d' % (argv, rc)
+  print('Command %r returned exit code %d' % (argv, rc))
   return rc
 
 
@@ -117,7 +117,7 @@
   def convert_trie_to_flat_paths(trie, prefix=None):
     # Also see blinkpy.web_tests.layout_package.json_results_generator
     result = {}
-    for name, data in trie.iteritems():
+    for name, data in trie.items():
       if prefix:
         name = prefix + test_separator + name
       if len(data) and not 'actual' in data and not 'expected' in data:
@@ -141,7 +141,7 @@
   passing_statuses = ('PASS', 'SLOW', 'NEEDSREBASELINE')
 
   for test, result in convert_trie_to_flat_paths(
-      json_results['tests']).iteritems():
+      json_results['tests']).items():
     key = 'unexpected_' if result.get('is_unexpected') else ''
     data = result['actual']
     actual_results = data.split()
@@ -200,7 +200,7 @@
   mapping = {}
 
   for cur_iteration_data in output.get('per_iteration_data', []):
-    for test_fullname, results in cur_iteration_data.iteritems():
+    for test_fullname, results in cur_iteration_data.items():
       # Results is a list with one entry per test try. Last one is the final
       # result.
       last_result = results[-1]
@@ -355,13 +355,13 @@
     valid = True
     try:
       env['CHROME_HEADLESS'] = '1'
-      print 'Running command: %s\nwith env: %r' % (
-          ' '.join(cmd), env)
+      print('Running command: %s\nwith env: %r' % (
+          ' '.join(cmd), env))
       if self.options.xvfb:
         exit_code = xvfb.run_executable(cmd, env)
       else:
         exit_code = test_env.run_command(cmd, env=env)
-      print 'Command returned exit code %d' % exit_code
+      print('Command returned exit code %d' % exit_code)
       return exit_code
     except Exception:
       traceback.print_exc()
diff --git a/testing/test_env.py b/testing/test_env.py
index eac46bf..4c3332a 100755
--- a/testing/test_env.py
+++ b/testing/test_env.py
@@ -5,6 +5,8 @@
 
 """Sets environment variables needed to run a chromium unit test."""
 
+from __future__ import print_function
+
 import io
 import os
 import signal
@@ -167,12 +169,12 @@
     p = subprocess.Popen(symbolize_command, stderr=subprocess.PIPE, env=env)
     (_, stderr) = p.communicate()
   except OSError as e:
-    print >> sys.stderr, 'Exception while symbolizing snippets: %s' % e
+    print('Exception while symbolizing snippets: %s' % e, file=sys.stderr)
     raise
 
   if p.returncode != 0:
-    print >> sys.stderr, "Error: failed to symbolize snippets in JSON:\n"
-    print >> sys.stderr, stderr
+    print("Error: failed to symbolize snippets in JSON:\n", file=sys.stderr)
+    print(stderr, file=sys.stderr)
     raise subprocess.CalledProcessError(p.returncode, symbolize_command)
 
 
@@ -185,7 +187,7 @@
   Returns:
     integer returncode of the subprocess.
   """
-  print('Running %r in %r (env: %r)' % (argv, cwd, env))
+  print(('Running %r in %r (env: %r)' % (argv, cwd, env)))
   assert stdoutfile
   with io.open(stdoutfile, 'wb') as writer, \
       io.open(stdoutfile, 'rb', 1) as reader:
@@ -199,7 +201,7 @@
       time.sleep(0.1)
     # Read the remaining.
     sys.stdout.write(reader.read())
-    print('Command %r returned exit code %d' % (argv, process.returncode))
+    print(('Command %r returned exit code %d' % (argv, process.returncode)))
     return process.returncode
 
 
@@ -213,7 +215,7 @@
     integer returncode of the subprocess.
   """
   if log:
-    print('Running %r in %r (env: %r)' % (argv, cwd, env))
+    print(('Running %r in %r (env: %r)' % (argv, cwd, env)))
   process = _popen(argv, env=env, cwd=cwd, stderr=subprocess.STDOUT)
   forward_signals([process])
   return wait_with_signals(process)
@@ -228,12 +230,12 @@
   Returns:
     integer returncode of the subprocess.
   """
-  print('Running %r in %r (env: %r)' % (argv, cwd, env))
+  print(('Running %r in %r (env: %r)' % (argv, cwd, env)))
   process = _popen(
       argv, env=env, cwd=cwd, stderr=file_handle, stdout=file_handle)
   forward_signals([process])
   exit_code = wait_with_signals(process)
-  print('Command returned exit code %d' % exit_code)
+  print(('Command returned exit code %d' % exit_code))
   return exit_code
 
 
@@ -342,11 +344,11 @@
       if env_var_name in env:
           env_to_print[env_var_name] = env[env_var_name]
 
-  print('Additional test environment:\n%s\n'
+  print(('Additional test environment:\n%s\n'
         'Command: %s\n' % (
         '\n'.join('    %s=%s' %
-            (k, v) for k, v in sorted(env_to_print.iteritems())),
-        ' '.join(cmd)))
+            (k, v) for k, v in sorted(env_to_print.items())),
+        ' '.join(cmd))))
   sys.stdout.flush()
   env.update(extra_env or {})
   try:
@@ -371,7 +373,7 @@
     else:
       return run_command(cmd, env=env, log=False)
   except OSError:
-    print >> sys.stderr, 'Failed to start %s' % cmd
+    print('Failed to start %s' % cmd, file=sys.stderr)
     raise