tast: Fix utf-8 encoding.

json.load() family parses JSON stringified format.
For each string literals and object keys are unicode string.
However, in most places, utf-8 encoded string is expected.
This CL fixes the encode of the returned json object.

BUG=chromium:992302
TEST=Ran locally. Ran python -m unittest server.site_tests.tast.tast_unittest.

Change-Id: Iadce7672a5c8693d740521c902460f68a47bc917
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/autotest/+/1746351
Commit-Queue: Hidehiko Abe <hidehiko@chromium.org>
Tested-by: Hidehiko Abe <hidehiko@chromium.org>
Legacy-Commit-Queue: Commit Bot <commit-bot@chromium.org>
Reviewed-by: Shuhei Takahashi <nya@chromium.org>
Auto-Submit: Hidehiko Abe <hidehiko@chromium.org>
diff --git a/server/site_tests/tast/tast.py b/server/site_tests/tast/tast.py
index 9833014..33b96d7 100644
--- a/server/site_tests/tast/tast.py
+++ b/server/site_tests/tast/tast.py
@@ -21,6 +21,20 @@
 _UNIX_EPOCH = dateutil.parser.parse('1970-01-01T00:00:00Z')
 
 
+def _encode_utf8_json(j):
+    """Takes JSON object parsed by json.load() family, and encode each unicode
+    strings into utf-8.
+    """
+    if isinstance(j, unicode):
+        return j.encode('utf-8')
+    if isinstance(j, list):
+        return [_encode_utf8_json(x) for x in j]
+    if isinstance(j, dict):
+        return dict((_encode_utf8_json(k), _encode_utf8_json(v))
+                    for k, v in j.iteritems())
+    return j
+
+
 class tast(test.test):
     """Autotest server test that runs a Tast test suite.
 
@@ -315,7 +329,8 @@
             args.append('-downloadprivatebundles=true')
         result = self._run_tast('list', args, self._LIST_TIMEOUT_SEC)
         try:
-            self._tests_to_run = json.loads(result.stdout.strip())
+            self._tests_to_run = _encode_utf8_json(
+                json.loads(result.stdout.strip()))
         except ValueError as e:
             raise error.TestFail('Failed to parse tests: %s' % str(e))
         if len(self._tests_to_run) == 0:
@@ -391,7 +406,7 @@
                 if not line:
                     continue
                 try:
-                    test = json.loads(line)
+                    test = _encode_utf8_json(json.loads(line))
                 except ValueError as e:
                     raise error.TestFail('Failed to parse %s: %s' % (path, e))
                 self._test_results.append(test)
diff --git a/server/site_tests/tast/tast_unittest.py b/server/site_tests/tast/tast_unittest.py
index 7b23897..bb2a8da 100755
--- a/server/site_tests/tast/tast_unittest.py
+++ b/server/site_tests/tast/tast_unittest.py
@@ -1,4 +1,5 @@
 #!/usr/bin/python
+# -*- coding: utf-8 -*-
 # Copyright 2018 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.
@@ -484,6 +485,14 @@
         self.assertEqual('1 missing: t1', msg([], ['t1']))
         self.assertEqual('1 missing: t2', msg(['t1'], ['t2']))
 
+    def testNonAsciiFailureMessage(self):
+        """Tests that non-ascii failure message should be handled correctly"""
+        tests = [TestInfo('pkg.Test', 0, 2, errors=[('失敗', 1)])]
+        self._init_tast_commands(tests)
+        self._run_test(ignore_test_failures=True)
+        self.assertEqual(status_string(get_status_entries_from_tests(tests)),
+                         status_string(self._job.status_entries))
+
     def testRunPrivateTests(self):
         """Tests running private tests."""
         self._init_tast_commands([TestInfo('pkg.Test', 0, 0)],