Skip to content

Commit 0d29dbf

Browse files
author
Vasileios Karakasis
authored
Merge pull request #1665 from vkarak/bugfix/jsonext-dump-deferrable-no-eval
[bugfix] Do not evaluate deferred expressions when dumping in JSON
2 parents 7ddadba + 1e13812 commit 0d29dbf

File tree

3 files changed

+40
-13
lines changed

3 files changed

+40
-13
lines changed

reframe/core/deferrable.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,22 +42,35 @@ def __init__(self, fn, *args, **kwargs):
4242
self._args = args
4343
self._kwargs = kwargs
4444

45+
# We cache the value of the last evaluation inside a tuple.
46+
# We don't cache the value directly, because it can be any.
47+
48+
# NOTE: The cache for the moment is only used by
49+
# `__rfm_json_encode__`. Enabling caching in the evaluation is a
50+
# reasonable optimization, but might break compatibility, so it needs
51+
# to be thought thoroughly and communicated properly in the
52+
# documentation.
53+
self._cached = ()
54+
4555
def evaluate(self):
4656
fn_args = []
4757
for arg in self._args:
4858
fn_args.append(
49-
arg.evaluate() if isinstance(arg, type(self)) else arg)
59+
arg.evaluate() if isinstance(arg, type(self)) else arg
60+
)
5061

5162
fn_kwargs = {}
5263
for k, v in self._kwargs.items():
5364
fn_kwargs[k] = (
54-
v.evaluate() if isinstance(v, type(self)) else v)
65+
v.evaluate() if isinstance(v, type(self)) else v
66+
)
5567

5668
ret = self._fn(*fn_args, **fn_kwargs)
5769
if isinstance(ret, type(self)):
58-
return ret.evaluate()
59-
else:
60-
return ret
70+
ret = ret.evaluate()
71+
72+
self._cached = (ret,)
73+
return ret
6174

6275
def __bool__(self):
6376
'''The truthy value of a deferred expression.
@@ -76,10 +89,10 @@ def __iter__(self):
7689
return iter(self.evaluate())
7790

7891
def __rfm_json_encode__(self):
79-
try:
80-
return self.evaluate()
81-
except BaseException:
92+
if self._cached == ():
8293
return None
94+
else:
95+
return self._cached[0]
8396

8497
# Overload Python operators to be able to defer any expression
8598
#
@@ -258,7 +271,7 @@ def __ror__(a, b):
258271
# Augmented operators
259272
#
260273
# NOTE: These are usually part of mutable objects, however
261-
# _DeferredExpression remains immutable, since it evnentually delegates
274+
# _DeferredExpression remains immutable, since it eventually delegates
262275
# their evaluation to the objects it wraps
263276
@deferrable
264277
def __iadd__(a, b):

unittests/test_logging.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ def test_logger_dynamic_attributes_deferrables(logfile, logger_with_check):
174174
)
175175
logger_with_check.logger.handlers[0].setFormatter(formatter)
176176
logger_with_check.info('xxx')
177+
assert _pattern_in_logfile(r'null\|null', logfile)
178+
179+
# Evaluate the deferrable and log again
180+
logger_with_check.check.deferred.evaluate()
181+
logger_with_check.info('xxx')
177182
assert _pattern_in_logfile(r'"hello"\|null', logfile)
178183

179184

unittests/test_utility.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1467,21 +1467,30 @@ def test_jsonext_dump(tmp_path):
14671467
with open(json_dump, 'w') as fp:
14681468
jsonext.dump({'foo': sn.defer(['bar'])}, fp)
14691469

1470+
with open(json_dump, 'r') as fp:
1471+
assert '{"foo": null}' == fp.read()
1472+
1473+
with open(json_dump, 'w') as fp:
1474+
jsonext.dump({'foo': sn.defer(['bar']).evaluate()}, fp)
1475+
14701476
with open(json_dump, 'r') as fp:
14711477
assert '{"foo": ["bar"]}' == fp.read()
14721478

14731479
with open(json_dump, 'w') as fp:
14741480
jsonext.dump({'foo': sn.defer(['bar'])}, fp, separators=(',', ':'))
14751481

14761482
with open(json_dump, 'r') as fp:
1477-
assert '{"foo":["bar"]}' == fp.read()
1483+
assert '{"foo":null}' == fp.read()
14781484

14791485

14801486
def test_jsonext_dumps():
14811487
assert '"foo"' == jsonext.dumps('foo')
1482-
assert '{"foo": ["bar"]}' == jsonext.dumps({'foo': sn.defer(['bar'])})
1483-
assert '{"foo":["bar"]}' == jsonext.dumps({'foo': sn.defer(['bar'])},
1484-
separators=(',', ':'))
1488+
assert '{"foo": ["bar"]}' == jsonext.dumps(
1489+
{'foo': sn.defer(['bar']).evaluate()}
1490+
)
1491+
assert '{"foo":["bar"]}' == jsonext.dumps(
1492+
{'foo': sn.defer(['bar']).evaluate()}, separators=(',', ':')
1493+
)
14851494

14861495

14871496
# Classes to test JSON deserialization

0 commit comments

Comments
 (0)