Skip to content

Commit 1e13812

Browse files
author
Vasileios Karakasis
committed
Do not evaluate deferred expressions when dumping in JSON.
We instead cache the last evaluated value and returned that one. If the deferred expression has never been evaluated, then `None` is returned.
1 parent 7ddadba commit 1e13812

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)