From 06dcbd4381870cd44c9b76e7a7be2c0431086264 2010-09-01 03:32:35 From: Fernando Perez Date: 2010-09-01 03:32:35 Subject: [PATCH] Fully refactored subprocess handling on all platforms. Now we have all process-related code in utils.process, which itself imports from platform-specific files as needed. On posix, we have reliable asynchronous delivery of stdout and stderr, and on win32 at least we have the basics that subprocess.py provides, since pexpect is not available. We also now support robust killing of subprocesses that may capture SIGINT: one SIGINT on our end is sent to the subprocess, but then we kill it, to prevent a rogue subprocess from hijacking the ipython console. Note that on posix, we now depend on pexpect, but we ship our own copy to users which we'll use if there's no system pexpect installed. UNC path handling for windows was implemented as a context manager called AvoidUNCPath. --- diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 64d52b3..441a000 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -57,7 +57,7 @@ from IPython.utils.doctestreload import doctest_reload from IPython.utils.io import ask_yes_no, rprint from IPython.utils.ipstruct import Struct from IPython.utils.path import get_home_dir, get_ipython_dir, HomeDirError -from IPython.utils.process import system, getoutput, getoutputerror +from IPython.utils.process import system, getoutput from IPython.utils.strdispatch import StrDispatch from IPython.utils.syspathcontext import prepended_to_syspath from IPython.utils.text import num_ini_spaces @@ -1667,12 +1667,6 @@ class InteractiveShell(Configurable, Magic): raise OSError("Background processes not supported.") return getoutput(self.var_expand(cmd, depth=2)) - def getoutputerror(self, cmd): - """Get stdout and stderr from a subprocess.""" - if cmd.endswith('&'): - raise OSError("Background processes not supported.") - return getoutputerror(self.var_expand(cmd, depth=2)) - #------------------------------------------------------------------------- # Things related to aliases #------------------------------------------------------------------------- diff --git a/IPython/core/magic.py b/IPython/core/magic.py index 8ba1ba9..3ce9ce8 100644 --- a/IPython/core/magic.py +++ b/IPython/core/magic.py @@ -3072,9 +3072,7 @@ Defaulting color scheme to 'NoColor'""" except ValueError: var,cmd = '','' # If all looks ok, proceed - out,err = self.shell.getoutputerror(cmd) - if err: - print >> IPython.utils.io.Term.cerr, err + out = self.shell.getoutput(cmd) if opts.has_key('l'): out = SList(out.split('\n')) else: @@ -3122,10 +3120,9 @@ Defaulting color scheme to 'NoColor'""" system commands.""" if parameter_s: - out,err = self.shell.getoutputerror(parameter_s) - if err: - print >> IPython.utils.io.Term.cerr, err - return SList(out.split('\n')) + out = self.shell.getoutput(parameter_s) + if out is not None: + return SList(out.splitlines()) def magic_r(self, parameter_s=''): """Repeat previous input. diff --git a/IPython/core/page.py b/IPython/core/page.py index e8a234e..ae905e9 100755 --- a/IPython/core/page.py +++ b/IPython/core/page.py @@ -37,7 +37,7 @@ from IPython.core.error import TryNext from IPython.utils.cursesimport import use_curses from IPython.utils.data import chop import IPython.utils.io -from IPython.utils.process import xsys +from IPython.utils.process import system from IPython.utils.terminal import get_terminal_size @@ -210,7 +210,7 @@ def page_file(fname, start=0, pager_cmd=None): try: if os.environ['TERM'] in ['emacs','dumb']: raise EnvironmentError - xsys(pager_cmd + ' ' + fname) + system(pager_cmd + ' ' + fname) except: try: if start > 0: diff --git a/IPython/utils/_process_common.py b/IPython/utils/_process_common.py new file mode 100644 index 0000000..37ad198 --- /dev/null +++ b/IPython/utils/_process_common.py @@ -0,0 +1,119 @@ +"""Common utilities for the various process_* implementations. + +This file is only meant to be imported by the platform-specific implementations +of subprocess utilities, and it contains tools that are common to all of them. +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2010 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- +import subprocess +import sys + +#----------------------------------------------------------------------------- +# Function definitions +#----------------------------------------------------------------------------- + +def read_no_interrupt(p): + """Read from a pipe ignoring EINTR errors. + + This is necessary because when reading from pipes with GUI event loops + running in the background, often interrupts are raised that stop the + command from completing.""" + import errno + + try: + return p.read() + except IOError, err: + if err.errno != errno.EINTR: + raise + + +def process_handler(cmd, callback, stderr=subprocess.PIPE): + """Open a command in a shell subprocess and execute a callback. + + This function provides common scaffolding for creating subprocess.Popen() + calls. It creates a Popen object and then calls the callback with it. + + Parameters + ---------- + cmd : str + A string to be executed with the underlying system shell (by calling + :func:`Popen` with ``shell=True``. + + callback : callable + A one-argument function that will be called with the Popen object. + + stderr : file descriptor number, optional + By default this is set to ``subprocess.PIPE``, but you can also pass the + value ``subprocess.STDOUT`` to force the subprocess' stderr to go into + the same file descriptor as its stdout. This is useful to read stdout + and stderr combined in the order they are generated. + + Returns + ------- + The return value of the provided callback is returned. + """ + sys.stdout.flush() + sys.stderr.flush() + p = subprocess.Popen(cmd, shell=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=stderr, + close_fds=True) + + try: + out = callback(p) + except KeyboardInterrupt: + print('^C') + sys.stdout.flush() + sys.stderr.flush() + out = None + finally: + # Make really sure that we don't leave processes behind, in case the + # call above raises an exception + # We start by assuming the subprocess finished (to avoid NameErrors + # later depending on the path taken) + if p.returncode is None: + try: + p.terminate() + p.poll() + except OSError: + pass + # One last try on our way out + if p.returncode is None: + try: + p.kill() + except OSError: + pass + + return out + + +def getoutputerror(cmd): + """Return (standard output, standard error) of executing cmd in a shell. + + Accepts the same arguments as os.system(). + + Parameters + ---------- + cmd : str + A command to be executed in the system shell. + + Returns + ------- + stdout : str + stderr : str + """ + + out_err = process_handler(cmd, lambda p: p.communicate()) + if out_err is None: + out_err = '', '' + return out_err diff --git a/IPython/utils/_process_posix.py b/IPython/utils/_process_posix.py new file mode 100644 index 0000000..46a40b4 --- /dev/null +++ b/IPython/utils/_process_posix.py @@ -0,0 +1,169 @@ +"""Posix-specific implementation of process utilities. + +This file is only meant to be imported by process.py, not by end-users. +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2010 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- +from __future__ import print_function + +# Stdlib +import subprocess as sp +import sys + +# Third-party +# We ship our own copy of pexpect (it's a single file) to minimize dependencies +# for users, but it's only used if we don't find the system copy. +try: + import pexpect +except ImportError: + from IPython.external import pexpect + +# Our own +from .autoattr import auto_attr + +#----------------------------------------------------------------------------- +# Function definitions +#----------------------------------------------------------------------------- + +def _find_cmd(cmd): + """Find the full path to a command using which.""" + + return sp.Popen(['/usr/bin/env', 'which', cmd], + stdout=sp.PIPE).communicate()[0] + + +class ProcessHandler(object): + """Execute subprocesses under the control of pexpect. + """ + # Timeout in seconds to wait on each reading of the subprocess' output. + # This should not be set too low to avoid cpu overusage from our side, + # since we read in a loop whose period is controlled by this timeout. + read_timeout = 0.05 + + # Timeout to give a process if we receive SIGINT, between sending the + # SIGINT to the process and forcefully terminating it. + terminate_timeout = 0.2 + + # File object where stdout and stderr of the subprocess will be written + logfile = None + + # Shell to call for subprocesses to execute + sh = None + + @auto_attr + def sh(self): + sh = pexpect.which('sh') + if sh is None: + raise OSError('"sh" shell not found') + return sh + + def __init__(self, logfile=None, read_timeout=None, terminate_timeout=None): + """Arguments are used for pexpect calls.""" + self.read_timeout = (ProcessHandler.read_timeout if read_timeout is + None else read_timeout) + self.terminate_timeout = (ProcessHandler.terminate_timeout if + terminate_timeout is None else + terminate_timeout) + self.logfile = sys.stdout if logfile is None else logfile + + def getoutput(self, cmd): + """Run a command and return its stdout/stderr as a string. + + Parameters + ---------- + cmd : str + A command to be executed in the system shell. + + Returns + ------- + output : str + A string containing the combination of stdout and stderr from the + subprocess, in whatever order the subprocess originally wrote to its + file descriptors (so the order of the information in this string is the + correct order as would be seen if running the command in a terminal). + """ + pcmd = self._make_cmd(cmd) + try: + return pexpect.run(pcmd).replace('\r\n', '\n') + except KeyboardInterrupt: + print('^C', file=sys.stderr, end='') + + def system(self, cmd): + """Execute a command in a subshell. + + Parameters + ---------- + cmd : str + A command to be executed in the system shell. + + Returns + ------- + None : we explicitly do NOT return the subprocess status code, as this + utility is meant to be used extensively in IPython, where any return + value would trigger :func:`sys.displayhook` calls. + """ + pcmd = self._make_cmd(cmd) + # Patterns to match on the output, for pexpect. We read input and + # allow either a short timeout or EOF + patterns = [pexpect.TIMEOUT, pexpect.EOF] + # the index of the EOF pattern in the list. + EOF_index = 1 # Fix this index if you change the list!! + # The size of the output stored so far in the process output buffer. + # Since pexpect only appends to this buffer, each time we print we + # record how far we've printed, so that next time we only print *new* + # content from the buffer. + out_size = 0 + try: + # Since we're not really searching the buffer for text patterns, we + # can set pexpect's search window to be tiny and it won't matter. + # We only search for the 'patterns' timeout or EOF, which aren't in + # the text itself. + child = pexpect.spawn(pcmd, searchwindowsize=1) + flush = sys.stdout.flush + while True: + # res is the index of the pattern that caused the match, so we + # know whether we've finished (if we matched EOF) or not + res_idx = child.expect_list(patterns, self.read_timeout) + print(child.before[out_size:], end='') + flush() + # Update the pointer to what we've already printed + out_size = len(child.before) + if res_idx==EOF_index: + break + except KeyboardInterrupt: + # We need to send ^C to the process. The ascii code for '^C' is 3 + # (the character is known as ETX for 'End of Text', see + # curses.ascii.ETX). + child.sendline(chr(3)) + # Read and print any more output the program might produce on its + # way out. + try: + out_size = len(child.before) + child.expect_list(patterns, self.terminate_timeout) + print(child.before[out_size:], end='') + except KeyboardInterrupt: + # Impatient users tend to type it multiple times + pass + finally: + # Ensure the subprocess really is terminated + child.terminate(force=True) + + def _make_cmd(self, cmd): + return '%s -c "%s"' % (self.sh, cmd) + + + +# Make objects with a functional interface for outside use +__ph = ProcessHandler() + +system = __ph.system +getoutput = __ph.getoutput diff --git a/IPython/utils/_process_win32.py b/IPython/utils/_process_win32.py new file mode 100644 index 0000000..5c59634 --- /dev/null +++ b/IPython/utils/_process_win32.py @@ -0,0 +1,137 @@ +"""Windows-specific implementation of process utilities. + +This file is only meant to be imported by process.py, not by end-users. +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2010 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- +from __future__ import print_function + +# stdlib +import os +import sys + +from subprocess import STDOUT + +# our own imports +from ._process_common import read_no_interrupt, process_handler + +#----------------------------------------------------------------------------- +# Function definitions +#----------------------------------------------------------------------------- + +class AvoidUNCPath(object): + """A context manager to protect command execution from UNC paths. + + In the Win32 API, commands can't be invoked with the cwd being a UNC path. + This context manager temporarily changes directory to the 'C:' drive on + entering, and restores the original working directory on exit. + + The context manager returns the starting working directory *if* it made a + change and None otherwise, so that users can apply the necessary adjustment + to their system calls in the event of a change. + + Example + ------- + :: + cmd = 'dir' + with AvoidUNCPath() as path: + if path is not None: + cmd = '"pushd %s &&"%s' % (path, cmd) + os.system(cmd) + """ + def __enter__(self): + self.path = os.getcwd() + self.is_unc_path = self.path.startswith(r"\\") + if self.is_unc_path: + # change to c drive (as cmd.exe cannot handle UNC addresses) + os.chdir("C:") + return self.path + + def __exit__(self, exc_type, exc_value, traceback): + if self.is_unc_path: + os.chdir(self.path) + + +def _find_cmd(cmd): + """Find the full path to a .bat or .exe using the win32api module.""" + try: + from win32api import SearchPath + except ImportError: + raise ImportError('you need to have pywin32 installed for this to work') + else: + PATH = os.environ['PATH'] + extensions = ['.exe', '.com', '.bat', '.py'] + path = None + for ext in extensions: + try: + path = SearchPath(PATH, cmd + ext)[0] + except: + pass + if path is None: + raise OSError("command %r not found" % cmd) + else: + return path + + +def _system_body(p): + """Callback for _system.""" + for line in read_no_interrupt(p.stdout).splitlines(): + print(line, file=sys.stdout) + for line in read_no_interrupt(p.stderr).splitlines(): + print(line, file=sys.stderr) + + +def system(cmd): + """Win32 version of os.system() that works with network shares. + + Note that this implementation returns None, as meant for use in IPython. + + Parameters + ---------- + cmd : str + A command to be executed in the system shell. + + Returns + ------- + None : we explicitly do NOT return the subprocess status code, as this + utility is meant to be used extensively in IPython, where any return value + would trigger :func:`sys.displayhook` calls. + """ + with AvoidUNCPath() as path: + if path is not None: + cmd = '"pushd %s &&"%s' % (path, cmd) + process_handler(cmd, _system_body) + + +def getoutput(cmd): + """Return standard output of executing cmd in a shell. + + Accepts the same arguments as os.system(). + + Parameters + ---------- + cmd : str + A command to be executed in the system shell. + + Returns + ------- + stdout : str + """ + + with AvoidUNCPath() as path: + if path is not None: + cmd = '"pushd %s &&"%s' % (path, cmd) + out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT) + + if out is None: + out = '' + return out diff --git a/IPython/utils/autoattr.py b/IPython/utils/autoattr.py index 3252eaf..0536d98 100644 --- a/IPython/utils/autoattr.py +++ b/IPython/utils/autoattr.py @@ -1,6 +1,4 @@ -#!/usr/bin/env python -# encoding: utf-8 -"""Descriptor support for NIPY. +"""Descriptor utilities. Utilities to support special Python descriptors [1,2], in particular the use of a useful pattern for properties we call 'one time properties'. These are @@ -24,6 +22,10 @@ Notes ----- This module is taken from the NiPy project (http://neuroimaging.scipy.org/site/index.html), and is BSD licensed. + +Authors +------- +- Fernando Perez. """ #----------------------------------------------------------------------------- diff --git a/IPython/utils/path.py b/IPython/utils/path.py index 8e2e199..69cf58f 100644 --- a/IPython/utils/path.py +++ b/IPython/utils/path.py @@ -18,7 +18,7 @@ import os import sys import IPython -from IPython.utils.process import xsys +from IPython.utils.process import system from IPython.utils.importstring import import_item #----------------------------------------------------------------------------- @@ -341,5 +341,5 @@ def target_update(target,deps,cmd): command if target is outdated.""" if target_outdated(target,deps): - xsys(cmd) + system(cmd) diff --git a/IPython/utils/process.py b/IPython/utils/process.py index 3a2ac78..af5879d 100644 --- a/IPython/utils/process.py +++ b/IPython/utils/process.py @@ -13,13 +13,20 @@ Utilities for working with external processes. #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- +from __future__ import print_function +# Stdlib import os import sys import shlex -import subprocess -from IPython.utils.terminal import set_term_title +# Our own +if sys.platform == 'win32': + from ._process_win32 import _find_cmd, system, getoutput, AvoidUNCPath +else: + from ._process_posix import _find_cmd, system, getoutput + +from ._process_common import getoutputerror #----------------------------------------------------------------------------- # Code @@ -30,39 +37,6 @@ class FindCmdError(Exception): pass -def _find_cmd(cmd): - """Find the full path to a command using which.""" - return os.popen('which %s' % cmd).read().strip() - - -if os.name == 'posix': - def _find_cmd(cmd): - """Find the full path to a command using which.""" - return getoutputerror('/usr/bin/env which %s' % cmd)[0] - - -if sys.platform == 'win32': - def _find_cmd(cmd): - """Find the full path to a .bat or .exe using the win32api module.""" - try: - from win32api import SearchPath - except ImportError: - raise ImportError('you need to have pywin32 installed for this to work') - else: - PATH = os.environ['PATH'] - extensions = ['.exe', '.com', '.bat', '.py'] - path = None - for ext in extensions: - try: - path = SearchPath(PATH,cmd + ext)[0] - except: - pass - if path is None: - raise OSError("command %r not found" % cmd) - else: - return path - - def find_cmd(cmd): """Find absolute path to executable cmd in a cross platform manner. @@ -87,7 +61,7 @@ def find_cmd(cmd): if cmd == 'python': return os.path.abspath(sys.executable) try: - path = _find_cmd(cmd) + path = _find_cmd(cmd).rstrip() except OSError: raise FindCmdError('command could not be found: %s' % cmd) # which returns empty if not found @@ -147,28 +121,6 @@ def arg_split(s, posix=False): return list(lex) -def system(cmd, verbose=0, debug=0, header=''): - """Execute a system command, return its exit status. - - Options: - - - verbose (0): print the command to be executed. - - - debug (0): only print, do not actually execute. - - - header (''): Header to print on screen prior to the executed command (it - is only prepended to the command, no newlines are added). - - Note: a stateful version of this function is available through the - SystemExec class.""" - - stat = 0 - if verbose or debug: print header+cmd - sys.stdout.flush() - if not debug: stat = os.system(cmd) - return stat - - def abbrev_cwd(): """ Return abbreviated version of cwd, e.g. d:mydir """ cwd = os.getcwd().replace('\\','/') @@ -186,182 +138,3 @@ def abbrev_cwd(): return (drivepart + ( cwd == '/' and '/' or tail)) - - -# This function is used by ipython in a lot of places to make system calls. -# We need it to be slightly different under win32, due to the vagaries of -# 'network shares'. A win32 override is below. - -def shell(cmd, verbose=0, debug=0, header=''): - """Execute a command in the system shell, always return None. - - Options: - - - verbose (0): print the command to be executed. - - - debug (0): only print, do not actually execute. - - - header (''): Header to print on screen prior to the executed command (it - is only prepended to the command, no newlines are added). - - Note: this is similar to system(), but it returns None so it can - be conveniently used in interactive loops without getting the return value - (typically 0) printed many times.""" - - stat = 0 - if verbose or debug: print header+cmd - # flush stdout so we don't mangle python's buffering - sys.stdout.flush() - - if not debug: - set_term_title("IPy " + cmd) - os.system(cmd) - set_term_title("IPy " + abbrev_cwd()) - -# override shell() for win32 to deal with network shares -if os.name in ('nt','dos'): - - shell_ori = shell - - def shell(cmd, verbose=0, debug=0, header=''): - if os.getcwd().startswith(r"\\"): - path = os.getcwd() - # change to c drive (cannot be on UNC-share when issuing os.system, - # as cmd.exe cannot handle UNC addresses) - os.chdir("c:") - # issue pushd to the UNC-share and then run the command - try: - shell_ori('"pushd %s&&"'%path+cmd,verbose,debug,header) - finally: - os.chdir(path) - else: - shell_ori(cmd,verbose,debug,header) - - shell.__doc__ = shell_ori.__doc__ - - -def getoutput(cmd, verbose=0, debug=0, header='', split=0): - """Dummy substitute for perl's backquotes. - - Executes a command and returns the output. - - Accepts the same arguments as system(), plus: - - - split(0): if true, the output is returned as a list split on newlines. - - Note: a stateful version of this function is available through the - SystemExec class. - - This is pretty much deprecated and rarely used, getoutputerror may be - what you need. - - """ - - if verbose or debug: print header+cmd - if not debug: - pipe = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).stdout - output = pipe.read() - # stipping last \n is here for backwards compat. - if output.endswith('\n'): - output = output[:-1] - if split: - return output.split('\n') - else: - return output - - -# for compatibility with older naming conventions -xsys = system - - -def getoutputerror(cmd, verbose=0, debug=0, header='', split=0): - """Return (standard output,standard error) of executing cmd in a shell. - - Accepts the same arguments as system(), plus: - - - split(0): if true, each of stdout/err is returned as a list split on - newlines. - - Note: a stateful version of this function is available through the - SystemExec class.""" - - if verbose or debug: print header+cmd - if not cmd: - if split: - return [],[] - else: - return '','' - if not debug: - p = subprocess.Popen(cmd, shell=True, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - close_fds=True) - pin, pout, perr = (p.stdin, p.stdout, p.stderr) - - tout = pout.read().rstrip() - terr = perr.read().rstrip() - pin.close() - pout.close() - perr.close() - if split: - return tout.split('\n'),terr.split('\n') - else: - return tout,terr - - -class SystemExec: - """Access the system and getoutput functions through a stateful interface. - - Note: here we refer to the system and getoutput functions from this - library, not the ones from the standard python library. - - This class offers the system and getoutput functions as methods, but the - verbose, debug and header parameters can be set for the instance (at - creation time or later) so that they don't need to be specified on each - call. - - For efficiency reasons, there's no way to override the parameters on a - per-call basis other than by setting instance attributes. If you need - local overrides, it's best to directly call system() or getoutput(). - - The following names are provided as alternate options: - - xsys: alias to system - - bq: alias to getoutput - - An instance can then be created as: - >>> sysexec = SystemExec(verbose=1,debug=0,header='Calling: ') - """ - - def __init__(self, verbose=0, debug=0, header='', split=0): - """Specify the instance's values for verbose, debug and header.""" - self.verbose = verbose - self.debug = debug - self.header = header - self.split = split - - def system(self, cmd): - """Stateful interface to system(), with the same keyword parameters.""" - - system(cmd, self.verbose, self.debug, self.header) - - def shell(self, cmd): - """Stateful interface to shell(), with the same keyword parameters.""" - - shell(cmd, self.verbose, self.debug, self.header) - - xsys = system # alias - - def getoutput(self, cmd): - """Stateful interface to getoutput().""" - - return getoutput(cmd, self.verbose, self.debug, self.header, self.split) - - def getoutputerror(self, cmd): - """Stateful interface to getoutputerror().""" - - return getoutputerror(cmd, self.verbose, self.debug, self.header, self.split) - - bq = getoutput # alias - - diff --git a/IPython/utils/tests/test_process.py b/IPython/utils/tests/test_process.py index c0bae05..b89fedf 100644 --- a/IPython/utils/tests/test_process.py +++ b/IPython/utils/tests/test_process.py @@ -15,11 +15,14 @@ Tests for platutils.py #----------------------------------------------------------------------------- import sys +from unittest import TestCase import nose.tools as nt -from IPython.utils.process import find_cmd, FindCmdError, arg_split +from IPython.utils.process import (find_cmd, FindCmdError, arg_split, + system, getoutput, getoutputerror) from IPython.testing import decorators as dec +from IPython.testing import tools as tt #----------------------------------------------------------------------------- # Tests @@ -66,3 +69,27 @@ def test_arg_split(): ] for argstr, argv in tests: nt.assert_equal(arg_split(argstr), argv) + + +class SubProcessTestCase(TestCase, tt.TempFileMixin): + def setUp(self): + """Make a valid python temp file.""" + lines = ["from __future__ import print_function", + "import sys", + "print('on stdout', end='', file=sys.stdout)", + "print('on stderr', end='', file=sys.stderr)", + "sys.stdout.flush()", + "sys.stderr.flush()"] + self.mktmp('\n'.join(lines)) + + def test_system(self): + system('python "%s"' % self.fname) + + def test_getoutput(self): + out = getoutput('python "%s"' % self.fname) + self.assertEquals(out, 'on stdout') + + def test_getoutput(self): + out, err = getoutputerror('python "%s"' % self.fname) + self.assertEquals(out, 'on stdout') + self.assertEquals(err, 'on stderr') diff --git a/IPython/zmq/zmqshell.py b/IPython/zmq/zmqshell.py index 96b7f85..538caa2 100644 --- a/IPython/zmq/zmqshell.py +++ b/IPython/zmq/zmqshell.py @@ -19,9 +19,6 @@ from __future__ import print_function import inspect import os import re -import sys - -from subprocess import Popen, PIPE # Our own from IPython.core.interactiveshell import ( @@ -83,26 +80,6 @@ class ZMQInteractiveShell(InteractiveShell): displayhook_class = Type(ZMQDisplayHook) - def system(self, cmd): - cmd = self.var_expand(cmd, depth=2).strip() - - # Runnning a bacgkrounded process from within the gui isn't supported - # because we do p.wait() at the end. So instead of silently blocking - # we simply refuse to run in this mode, to avoid surprising the user. - if cmd.endswith('&'): - raise OSError("Background processes not supported.") - - sys.stdout.flush() - sys.stderr.flush() - p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE) - for line in p.stdout.read().split('\n'): - if len(line) > 0: - print(line) - for line in p.stderr.read().split('\n'): - if len(line) > 0: - print(line, file=sys.stderr) - p.wait() - def init_io(self): # This will just use sys.stdout and sys.stderr. If you want to # override sys.stdout and sys.stderr themselves, you need to do that