diff --git a/reframe/utility/sanity.py b/reframe/utility/sanity.py index cfce935026..0584a72e41 100644 --- a/reframe/utility/sanity.py +++ b/reframe/utility/sanity.py @@ -62,6 +62,7 @@ import glob as pyglob import itertools import re +import sys import reframe.utility as util from reframe.core.deferrable import deferrable, _DeferredExpression @@ -182,6 +183,36 @@ def min(*args): return builtins.min(*args) +@deferrable +def print(*objects, sep=' ', end='\n', file=None, flush=False): + '''Replacement for the built-in :func:`print() ` function. + + The only difference is that this function returns the ``objects``, so that + you can use it transparently inside a complex sanity expression. For + example, you could write the following to print the matches returned from + the :func:`extractall()` function: + + .. code:: python + + self.sanity_patterns = sn.assert_eq( + sn.count(sn.print(sn.extract_all(...))), 10 + ) + + If ``file`` is None, :func:`print` will print its arguments to the + standard output. Unlike the builtin :func:`print() ` + function, we don't bind the ``file`` argument to :attr:`sys.stdout` by + default. This would capture :attr:`sys.stdout` at the time this function + is defined and would prevent it from seeing changes to :attr:`sys.stdout`, + such as redirects, in the future. + ''' + + if file is None: + file = sys.stdout + + builtins.print(*objects, sep=sep, end=end, file=file, flush=flush) + return objects + + @deferrable def reversed(seq): '''Replacement for the built-in diff --git a/unittests/test_sanity_functions.py b/unittests/test_sanity_functions.py index ee3f2557e8..acb02ac31d 100644 --- a/unittests/test_sanity_functions.py +++ b/unittests/test_sanity_functions.py @@ -1,8 +1,13 @@ +import contextlib +import io import itertools import os +import sys import unittest + from tempfile import NamedTemporaryFile + import reframe.utility.sanity as sn from reframe.core.exceptions import SanityError from unittests.fixtures import TEST_RESOURCES_CHECKS @@ -124,6 +129,42 @@ def test_min(self): l.append(0) self.assertEqual(0, sn.min(dl)) + def test_print_stdout(self): + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + x, y = sn.evaluate(sn.print(1, sn.defer(2))) + + assert stdout.getvalue() == '1 2\n' + assert x == 1 + assert y == 2 + + def test_print_stderr(self): + stderr = io.StringIO() + with contextlib.redirect_stderr(stderr): + x, y = sn.evaluate(sn.print(1, sn.defer(2), file=sys.stderr)) + + assert stderr.getvalue() == '1 2\n' + assert x == 1 + assert y == 2 + + def test_print_separator(self): + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + x, y = sn.evaluate(sn.print(1, sn.defer(2), sep='|')) + + assert stdout.getvalue() == '1|2\n' + assert x == 1 + assert y == 2 + + def test_print_end(self): + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + x, y = sn.evaluate(sn.print(1, sn.defer(2), end='')) + + assert stdout.getvalue() == '1 2' + assert x == 1 + assert y == 2 + def test_reversed(self): l = [1, 2, 3] dr = sn.reversed(l)