diff --git a/reframe/core/deferrable.py b/reframe/core/deferrable.py index 924606b88b..e3274bda64 100644 --- a/reframe/core/deferrable.py +++ b/reframe/core/deferrable.py @@ -77,6 +77,9 @@ def __iter__(self): '''Evaluate the deferred expression and iterate over the result.''' return iter(self.evaluate()) + def __rfm_json_encode__(self): + return self.evaluate() + # Overload Python operators to be able to defer any expression # # NOTE: In the following we are not using `self` for denoting the first diff --git a/reframe/frontend/cli.py b/reframe/frontend/cli.py index a7e961dd5a..35c2ea2453 100644 --- a/reframe/frontend/cli.py +++ b/reframe/frontend/cli.py @@ -21,6 +21,7 @@ import reframe.frontend.check_filters as filters import reframe.frontend.dependency as dependency import reframe.utility.os_ext as os_ext +import reframe.utility.json as jsonext from reframe.core.exceptions import ( EnvironError, ConfigError, ReframeError, ReframeDeprecationWarning, ReframeFatalError, @@ -810,7 +811,7 @@ def print_infoline(param, value): report_file = generate_report_filename(report_file) try: with open(report_file, 'w') as fp: - json.dump(json_report, fp, indent=2) + jsonext.dump(json_report, fp, indent=2) except OSError as e: printer.warning( f'failed to generate report in {report_file!r}: {e}' diff --git a/reframe/utility/json.py b/reframe/utility/json.py new file mode 100644 index 0000000000..2d7e4675f7 --- /dev/null +++ b/reframe/utility/json.py @@ -0,0 +1,24 @@ +# Copyright 2016-2020 Swiss National Supercomputing Centre (CSCS/ETH Zurich) +# ReFrame Project Developers. See the top-level LICENSE file for details. +# +# SPDX-License-Identifier: BSD-3-Clause + +import json + + +class _ReframeJsonEncoder(json.JSONEncoder): + def default(self, obj): + if hasattr(obj, '__rfm_json_encode__'): + return obj.__rfm_json_encode__() + + return json.JSONEncoder.default(self, obj) + + +def dump(obj, fp, **kwargs): + kwargs['cls'] = _ReframeJsonEncoder + return json.dump(obj, fp, **kwargs) + + +def dumps(obj, **kwargs): + kwargs['cls'] = _ReframeJsonEncoder + return json.dumps(obj, **kwargs) diff --git a/unittests/test_utility.py b/unittests/test_utility.py index 4cc1329334..8465d3f9f0 100644 --- a/unittests/test_utility.py +++ b/unittests/test_utility.py @@ -12,7 +12,9 @@ import reframe import reframe.core.fields as fields import reframe.utility as util +import reframe.utility.json as jsonext import reframe.utility.os_ext as os_ext +import reframe.utility.sanity as sn from reframe.core.exceptions import (SpawnedProcessError, SpawnedProcessTimeout) @@ -1293,3 +1295,25 @@ def test_cray_cle_info_missing_parts(tmp_path): assert cle_info.date is None assert cle_info.network is None assert cle_info.patchset == '09' + + +def test_jsonext_dump(tmp_path): + json_dump = tmp_path / 'test.json' + with open(json_dump, 'w') as fp: + jsonext.dump({'foo': sn.defer(['bar'])}, fp) + + with open(json_dump, 'r') as fp: + assert '{"foo": ["bar"]}' == fp.read() + + with open(json_dump, 'w') as fp: + jsonext.dump({'foo': sn.defer(['bar'])}, fp, separators=(',', ':')) + + with open(json_dump, 'r') as fp: + assert '{"foo":["bar"]}' == fp.read() + + +def test_jsonext_dumps(): + assert '"foo"' == jsonext.dumps('foo') + assert '{"foo": ["bar"]}' == jsonext.dumps({'foo': sn.defer(['bar'])}) + assert '{"foo":["bar"]}' == jsonext.dumps({'foo': sn.defer(['bar'])}, + separators=(',', ':'))