Repository: airspeed-velocity/asv

Issue:
JSON serialization problem
"TypeError: Object of type 'bytes' is not JSON serializable" during asv run --profile
Python 3.6

Discussion:

Patch:
diff --git a/asv/results.py b/asv/results.py
index 76dbe6b..8513b71 100644
--- a/asv/results.py
+++ b/asv/results.py
@@ -4,6 +4,7 @@
 from __future__ import (absolute_import, division, print_function,
                         unicode_literals)
 
+import sys
 import base64
 import os
 import zlib
@@ -405,15 +406,31 @@ class Results(object):
         self._benchmark_version[benchmark_name] = benchmark_version
 
         if 'profile' in result and result['profile']:
-            self._profiles[benchmark_name] = base64.b64encode(
+            profile_data = base64.b64encode(
                 zlib.compress(result['profile']))
+            if sys.version_info[0] >= 3:
+                profile_data = profile_data.decode('ascii')
+            self._profiles[benchmark_name] = profile_data
 
     def get_profile(self, benchmark_name):
         """
         Get the profile data for the given benchmark name.
+
+        Parameters
+        ----------
+        benchmark_name : str
+            Name of benchmark
+
+        Returns
+        -------
+        profile_data : bytes
+            Raw profile data
+
         """
-        return zlib.decompress(
-            base64.b64decode(self._profiles[benchmark_name]))
+        profile_data = self._profiles[benchmark_name]
+        if sys.version_info[0] >= 3:
+            profile_data = profile_data.encode('ascii')
+        return zlib.decompress(base64.b64decode(profile_data))
 
     def has_profile(self, benchmark_name):
         """
diff --git a/asv/util.py b/asv/util.py
index 748b9ba..34abb88 100644
--- a/asv/util.py
+++ b/asv/util.py
@@ -643,7 +643,11 @@ def write_json(path, data, api_version=None):
         data = dict(data)
         data['version'] = api_version
 
-    with long_path_open(path, 'w') as fd:
+    open_kwargs = {}
+    if sys.version_info[0] >= 3:
+        open_kwargs['encoding'] = 'utf-8'
+
+    with long_path_open(path, 'w', **open_kwargs) as fd:
         json.dump(data, fd, indent=4, sort_keys=True)
 
 
@@ -656,7 +660,11 @@ def load_json(path, api_version=None, cleanup=True):
 
     path = os.path.abspath(path)
 
-    with long_path_open(path, 'r') as fd:
+    open_kwargs = {}
+    if sys.version_info[0] >= 3:
+        open_kwargs['encoding'] = 'utf-8'
+
+    with long_path_open(path, 'r', **open_kwargs) as fd:
         content = fd.read()
 
     if cleanup:


Test patch:
diff --git a/test/test_results.py b/test/test_results.py
index 2a7c0fb..d983a4a 100644
--- a/test/test_results.py
+++ b/test/test_results.py
@@ -35,13 +35,13 @@ def test_results(tmpdir):
         values = {
             'suite1.benchmark1': {'result': [float(i * 0.001)], 'stats': [{'foo': 1}],
                                   'samples': [[1,2]], 'number': [6], 'params': [['a']],
-                                  'version': "1"},
+                                  'version': "1", 'profile': b'\x00\xff'},
             'suite1.benchmark2': {'result': [float(i * i * 0.001)], 'stats': [{'foo': 2}],
                                   'samples': [[3,4]], 'number': [7], 'params': [],
-                                  'version': "1"},
+                                  'version': "1", 'profile': b'\x00\xff'},
             'suite2.benchmark1': {'result': [float((i + 1) ** -1)], 'stats': [{'foo': 3}],
                                   'samples': [[5,6]], 'number': [8], 'params': [['c']],
-                                  'version': None}
+                                  'version': None, 'profile': b'\x00\xff'}
         }
 
         for key, val in values.items():
@@ -66,6 +66,7 @@ def test_results(tmpdir):
             assert rr._stats == r._stats
             assert rr._number == r._number
             assert rr._samples == r._samples
+            assert rr._profiles == r._profiles
             assert rr.started_at == r._started_at
             assert rr.ended_at == r._ended_at
             assert rr.benchmark_version == r._benchmark_version
@@ -87,6 +88,9 @@ def test_results(tmpdir):
             assert r2.get_result_stats(bench, bad_params) == [None, None]
             assert r2.get_result_samples(bench, bad_params) == ([None, None], [None, None])
 
+            # Get profile
+            assert r2.get_profile(bench) == b'\x00\xff'
+
         # Check get_result_keys
         mock_benchmarks = {
             'suite1.benchmark1': {'version': '1'},
diff --git a/test/test_util.py b/test/test_util.py
index d86167a..19c8ba7 100644
--- a/test/test_util.py
+++ b/test/test_util.py
@@ -269,3 +269,13 @@ def test_is_main_thread():
     thread.join()
 
     assert results == [False]
+
+
+def test_json_non_ascii(tmpdir):
+    non_ascii_data = [{'😼': '難', 'ä': 3}]
+
+    fn = os.path.join(str(tmpdir), "nonascii.json")
+    util.write_json(fn, non_ascii_data)
+    data = util.load_json(fn)
+
+    assert data == non_ascii_data
diff --git a/test/test_workflow.py b/test/test_workflow.py
index 2959f00..5e3983d 100644
--- a/test/test_workflow.py
+++ b/test/test_workflow.py
@@ -102,7 +102,7 @@ def test_run_publish(capfd, basic_conf):
 
     # Tests a typical complete run/publish workflow
     tools.run_asv_with_conf(conf, 'run', "master~5..master", '--steps=2',
-                            '--quick', '--show-stderr',
+                            '--quick', '--show-stderr', '--profile',
                             _machine_file=machine_file)
     text, err = capfd.readouterr()