summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/spack/llnl/util/tty/log.py46
-rw-r--r--lib/spack/spack/test/cmd/install.py21
-rw-r--r--var/spack/repos/builtin.mock/packages/printing-package/package.py45
3 files changed, 110 insertions, 2 deletions
diff --git a/lib/spack/llnl/util/tty/log.py b/lib/spack/llnl/util/tty/log.py
index 2b3ad22c2d..97547b34ed 100644
--- a/lib/spack/llnl/util/tty/log.py
+++ b/lib/spack/llnl/util/tty/log.py
@@ -124,6 +124,26 @@ class keyboard_input(object):
self.stream.fileno(), termios.TCSADRAIN, self.old_cfg)
+class Unbuffered(object):
+ """Wrapper for Python streams that forces them to be unbuffered.
+
+ This is implemented by forcing a flush after each write.
+ """
+ def __init__(self, stream):
+ self.stream = stream
+
+ def write(self, data):
+ self.stream.write(data)
+ self.stream.flush()
+
+ def writelines(self, datas):
+ self.stream.writelines(datas)
+ self.stream.flush()
+
+ def __getattr__(self, attr):
+ return getattr(self.stream, attr)
+
+
def _file_descriptors_work():
"""Whether we can get file descriptors for stdout and stderr.
@@ -184,18 +204,32 @@ class log_output(object):
work within test frameworks like nose and pytest.
"""
- def __init__(self, filename=None, echo=False, debug=False):
+ def __init__(self, filename=None, echo=False, debug=False, buffer=False):
"""Create a new output log context manager.
+ Args:
+ filename (str): name of file where output should be logged
+ echo (bool): whether to echo output in addition to logging it
+ debug (bool): whether to enable tty debug mode during logging
+ buffer (bool): pass buffer=True to skip unbuffering output; note
+ this doesn't set up any *new* buffering
+
+ By default, we unbuffer sys.stdout and sys.stderr because the
+ logger will include output from executed programs and from python
+ calls. If stdout and stderr are buffered, their output won't be
+ printed in the right place w.r.t. output from commands.
+
Logger daemon is not started until ``__enter__()``.
+
"""
self.filename = filename
self.echo = echo
self.debug = debug
+ self.buffer = buffer
self._active = False # used to prevent re-entry
- def __call__(self, filename=None, echo=None, debug=None):
+ def __call__(self, filename=None, echo=None, debug=None, buffer=None):
"""Thie behaves the same as init. It allows a logger to be reused.
With the ``__call__`` function, you can save state between uses
@@ -217,6 +251,8 @@ class log_output(object):
self.echo = echo
if debug is not None:
self.debug = debug
+ if buffer is not None:
+ self.buffer = buffer
return self
def __enter__(self):
@@ -297,6 +333,11 @@ class log_output(object):
sys.stdout = pipe_fd_out
sys.stderr = pipe_fd_out
+ # Unbuffer stdout and stderr at the Python level
+ if not self.buffer:
+ sys.stdout = Unbuffered(sys.stdout)
+ sys.stderr = Unbuffered(sys.stderr)
+
# Force color and debug settings now that we have redirected.
tty.color.set_color_when(forced_color)
tty._debug = self.debug
@@ -399,6 +440,7 @@ class log_output(object):
# Echo to stdout if requested or forced
if echo or force_echo:
sys.stdout.write(line)
+ sys.stdout.flush()
# Stripped output to log file.
log_file.write(_strip(line))
diff --git a/lib/spack/spack/test/cmd/install.py b/lib/spack/spack/test/cmd/install.py
index 5886d602a2..1b8549ca3d 100644
--- a/lib/spack/spack/test/cmd/install.py
+++ b/lib/spack/spack/test/cmd/install.py
@@ -23,10 +23,12 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
import argparse
+import os
import pytest
import spack.cmd.install
+from spack.spec import Spec
from spack.main import SpackCommand
install = SpackCommand('install')
@@ -86,3 +88,22 @@ def test_install_package_already_installed(
def test_install_dirty_flag(parser, arguments, expected):
args = parser.parse_args(arguments)
assert args.dirty == expected
+
+
+def test_package_output(tmpdir, capsys, install_mockery, mock_fetch):
+ """Ensure output printed from pkgs is captured by output redirection."""
+ # we can't use output capture here because it interferes with Spack's
+ # logging. TODO: see whether we can get multiple log_outputs to work
+ # when nested AND in pytest
+ spec = Spec('printing-package').concretized()
+ pkg = spec.package
+ pkg.do_install(verbose=True)
+
+ log_file = os.path.join(spec.prefix, '.spack', 'build.out')
+ with open(log_file) as f:
+ out = f.read()
+
+ # make sure that output from the actual package file appears in the
+ # right place in the build log.
+ assert "BEFORE INSTALL\n==> './configure'" in out
+ assert "'install'\nAFTER INSTALL" in out
diff --git a/var/spack/repos/builtin.mock/packages/printing-package/package.py b/var/spack/repos/builtin.mock/packages/printing-package/package.py
new file mode 100644
index 0000000000..2396677252
--- /dev/null
+++ b/var/spack/repos/builtin.mock/packages/printing-package/package.py
@@ -0,0 +1,45 @@
+##############################################################################
+# Copyright (c) 2013-2017, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/spack
+# Please also see the NOTICE and LICENSE files for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+from spack import *
+
+
+class PrintingPackage(Package):
+ """This package prints some output from its install method.
+
+ We use this to test whether that output is properly logged.
+ """
+ homepage = "http://www.example.com/printing_package"
+ url = "http://www.unit-test-should-replace-this-url/trivial_install-1.0.tar.gz"
+
+ version('1.0', 'foobarbaz')
+
+ def install(self, spec, prefix):
+ print("BEFORE INSTALL")
+
+ configure('--prefix=%s' % prefix)
+ make()
+ make('install')
+
+ print("AFTER INSTALL")