diff --git a/IPython/Extensions/ipy_greedycompleter.py b/IPython/Extensions/ipy_greedycompleter.py index 3b44751..37142d7 100644 --- a/IPython/Extensions/ipy_greedycompleter.py +++ b/IPython/Extensions/ipy_greedycompleter.py @@ -66,7 +66,7 @@ def attr_matches(self, text): return res def main(): - import readline + import IPython.rlineimpl as readline readline.set_completer_delims(" \n\t") # monkeypatch - the code will be folded to normal completer later on import IPython.completer diff --git a/IPython/UserConfig/ipy_user_conf.py b/IPython/UserConfig/ipy_user_conf.py index b0184ef..6d6fbbd 100644 --- a/IPython/UserConfig/ipy_user_conf.py +++ b/IPython/UserConfig/ipy_user_conf.py @@ -99,16 +99,14 @@ def main(): #import readline #readline.parse_and_bind('set completion-query-items 1000') #readline.parse_and_bind('set page-completions no') - - - - + + # some config helper functions you can use def import_all(modules): """ Usage: import_all("os sys") """ for m in modules.split(): ip.ex("from %s import *" % m) - + def execf(fname): """ Execute a file in user namespace """ ip.ex('execfile("%s")' % os.path.expanduser(fname)) diff --git a/IPython/frontend/asyncfrontendbase.py b/IPython/frontend/asyncfrontendbase.py index 78afa18..cc7ada6 100644 --- a/IPython/frontend/asyncfrontendbase.py +++ b/IPython/frontend/asyncfrontendbase.py @@ -14,12 +14,13 @@ __docformat__ = "restructuredtext en" #------------------------------------------------------------------------------- # Imports #------------------------------------------------------------------------------- -from IPython.external import guid +from IPython.external import guid from zope.interface import Interface, Attribute, implements, classProvides from twisted.python.failure import Failure -from IPython.frontend.frontendbase import FrontEndBase, IFrontEnd, IFrontEndFactory +from IPython.frontend.frontendbase import ( + FrontEndBase, IFrontEnd, IFrontEndFactory) from IPython.kernel.core.history import FrontEndHistory from IPython.kernel.engineservice import IEngineCore diff --git a/IPython/frontend/cocoa/tests/test_cocoa_frontend.py b/IPython/frontend/cocoa/tests/test_cocoa_frontend.py index eb4dae2..58b78e4 100644 --- a/IPython/frontend/cocoa/tests/test_cocoa_frontend.py +++ b/IPython/frontend/cocoa/tests/test_cocoa_frontend.py @@ -15,30 +15,38 @@ __docformat__ = "restructuredtext en" # Imports #--------------------------------------------------------------------------- +# Tell nose to skip this module +__test__ = {} + +from twisted.trial import unittest +from twisted.internet.defer import succeed + +from IPython.kernel.core.interpreter import Interpreter +import IPython.kernel.engineservice as es + try: - from IPython.kernel.core.interpreter import Interpreter - import IPython.kernel.engineservice as es - from IPython.testing.util import DeferredTestCase - from twisted.internet.defer import succeed - from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController + from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController from Foundation import NSMakeRect - from AppKit import NSTextView, NSScrollView + from AppKit import NSTextView, NSScrollView except ImportError: - import nose - raise nose.SkipTest("This test requires zope.interface, Twisted, Foolscap and PyObjC") + # This tells twisted.trial to skip this module if PyObjC is not found + skip = True -class TestIPythonCocoaControler(DeferredTestCase): +#--------------------------------------------------------------------------- +# Tests +#--------------------------------------------------------------------------- +class TestIPythonCocoaControler(unittest.TestCase): """Tests for IPythonCocoaController""" - + def setUp(self): self.controller = IPythonCocoaController.alloc().init() self.engine = es.EngineService() self.engine.startService() - + def tearDown(self): self.controller = None self.engine.stopService() - + def testControllerExecutesCode(self): code ="""5+5""" expected = Interpreter().execute(code) @@ -47,48 +55,46 @@ class TestIPythonCocoaControler(DeferredTestCase): del result['number'] del result['id'] return result - self.assertDeferredEquals( - self.controller.execute(code).addCallback(removeNumberAndID), - expected) - + d = self.controller.execute(code) + d.addCallback(removeNumberAndID) + d.addCallback(lambda r: self.assertEquals(r, expected)) + def testControllerMirrorsUserNSWithValuesAsStrings(self): code = """userns1=1;userns2=2""" def testControllerUserNS(result): self.assertEquals(self.controller.userNS['userns1'], 1) self.assertEquals(self.controller.userNS['userns2'], 2) - self.controller.execute(code).addCallback(testControllerUserNS) - - + def testControllerInstantiatesIEngine(self): self.assert_(es.IEngineBase.providedBy(self.controller.engine)) - + def testControllerCompletesToken(self): code = """longNameVariable=10""" def testCompletes(result): self.assert_("longNameVariable" in result) - + def testCompleteToken(result): self.controller.complete("longNa").addCallback(testCompletes) - + self.controller.execute(code).addCallback(testCompletes) - - + + def testCurrentIndent(self): """test that current_indent_string returns current indent or None. Uses _indent_for_block for direct unit testing. """ - + self.controller.tabUsesSpaces = True self.assert_(self.controller._indent_for_block("""a=3""") == None) self.assert_(self.controller._indent_for_block("") == None) block = """def test():\n a=3""" self.assert_(self.controller._indent_for_block(block) == \ ' ' * self.controller.tabSpaces) - + block = """if(True):\n%sif(False):\n%spass""" % \ (' '*self.controller.tabSpaces, 2*' '*self.controller.tabSpaces) self.assert_(self.controller._indent_for_block(block) == \ 2*(' '*self.controller.tabSpaces)) - + diff --git a/IPython/frontend/linefrontendbase.py b/IPython/frontend/linefrontendbase.py index a154e13..1cb1ad6 100644 --- a/IPython/frontend/linefrontendbase.py +++ b/IPython/frontend/linefrontendbase.py @@ -18,10 +18,8 @@ __docformat__ = "restructuredtext en" #------------------------------------------------------------------------------- import re -import IPython import sys import codeop -import traceback from frontendbase import FrontEndBase from IPython.kernel.core.interpreter import Interpreter @@ -58,6 +56,9 @@ class LineFrontEndBase(FrontEndBase): # programatic control of the frontend. last_result = dict(number=0) + # The last prompt displayed. Useful for continuation prompts. + last_prompt = '' + # The input buffer being edited input_buffer = '' @@ -151,8 +152,12 @@ class LineFrontEndBase(FrontEndBase): self.capture_output() try: # Add line returns here, to make sure that the statement is - # complete. - is_complete = codeop.compile_command(string.rstrip() + '\n\n', + # complete (except if '\' was used). + # This should probably be done in a different place (like + # maybe 'prefilter_input' method? For now, this works. + clean_string = string.rstrip('\n') + if not clean_string.endswith('\\'): clean_string +='\n\n' + is_complete = codeop.compile_command(clean_string, "", "exec") self.release_output() except Exception, e: @@ -183,16 +188,6 @@ class LineFrontEndBase(FrontEndBase): # Create a false result, in case there is an exception self.last_result = dict(number=self.prompt_number) - ## try: - ## self.history.input_cache[-1] = raw_string.rstrip() - ## result = self.shell.execute(python_string) - ## self.last_result = result - ## self.render_result(result) - ## except: - ## self.show_traceback() - ## finally: - ## self.after_execute() - try: try: self.history.input_cache[-1] = raw_string.rstrip() @@ -272,15 +267,15 @@ class LineFrontEndBase(FrontEndBase): symbols_per_line = max(1, chars_per_line/max_len) pos = 1 - buf = [] + completion_string = [] for symbol in possibilities: if pos < symbols_per_line: - buf.append(symbol.ljust(max_len)) + completion_string.append(symbol.ljust(max_len)) pos += 1 else: - buf.append(symbol.rstrip() + '\n') + completion_string.append(symbol.rstrip() + '\n') pos = 1 - self.write(''.join(buf)) + self.write(''.join(completion_string)) self.new_prompt(self.input_prompt_template.substitute( number=self.last_result['number'] + 1)) self.input_buffer = new_line @@ -297,26 +292,70 @@ class LineFrontEndBase(FrontEndBase): self.write(prompt) + def continuation_prompt(self): + """Returns the current continuation prompt. + """ + return ("."*(len(self.last_prompt)-2) + ': ') + + + def execute_command(self, command, hidden=False): + """ Execute a command, not only in the model, but also in the + view, if any. + """ + return self.shell.execute(command) + #-------------------------------------------------------------------------- # Private API #-------------------------------------------------------------------------- - def _on_enter(self): + def _on_enter(self, new_line_pos=0): """ Called when the return key is pressed in a line editing buffer. + + Parameters + ---------- + new_line_pos : integer, optional + Position of the new line to add, starting from the + end (0 adds a new line after the last line, -1 before + the last line...) + + Returns + ------- + True if execution is triggered """ current_buffer = self.input_buffer - cleaned_buffer = self.prefilter_input(current_buffer) + # XXX: This string replace is ugly, but there should be no way it + # fails. + prompt_less_buffer = re.sub('^' + self.continuation_prompt(), + '', current_buffer).replace('\n' + self.continuation_prompt(), + '\n') + cleaned_buffer = self.prefilter_input(prompt_less_buffer) if self.is_complete(cleaned_buffer): self.execute(cleaned_buffer, raw_string=current_buffer) + return True else: - self.input_buffer += self._get_indent_string( - current_buffer[:-1]) - if len(current_buffer.split('\n')) == 2: - self.input_buffer += '\t\t' - if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'): - self.input_buffer += '\t' - + # Start a new line. + new_line_pos = -new_line_pos + lines = current_buffer.split('\n')[:-1] + prompt_less_lines = prompt_less_buffer.split('\n') + # Create the new line, with the continuation prompt, and the + # same amount of indent than the line above it. + new_line = self.continuation_prompt() + \ + self._get_indent_string('\n'.join( + prompt_less_lines[:new_line_pos-1])) + if len(lines) == 1: + # We are starting a first continuation line. Indent it. + new_line += '\t' + elif current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'): + # The last line ends with ":", autoindent the new line. + new_line += '\t' + + if new_line_pos == 0: + lines.append(new_line) + else: + lines.insert(new_line_pos, new_line) + self.input_buffer = '\n'.join(lines) + def _get_indent_string(self, string): """ Return the string of whitespace that prefixes a line. Used to diff --git a/IPython/frontend/prefilterfrontend.py b/IPython/frontend/prefilterfrontend.py index 440d3e2..ceb21c2 100644 --- a/IPython/frontend/prefilterfrontend.py +++ b/IPython/frontend/prefilterfrontend.py @@ -22,9 +22,10 @@ __docformat__ = "restructuredtext en" # Imports #------------------------------------------------------------------------------- import sys - -from linefrontendbase import LineFrontEndBase, common_prefix -from frontendbase import FrontEndBase +import pydoc +import os +import re +import __builtin__ from IPython.ipmaker import make_IPython from IPython.ipapi import IPApi @@ -33,9 +34,8 @@ from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap from IPython.genutils import Term -import pydoc -import os -import sys + +from linefrontendbase import LineFrontEndBase, common_prefix def mk_system_call(system_call_function, command): @@ -45,6 +45,8 @@ def mk_system_call(system_call_function, command): """ def my_system_call(args): system_call_function("%s %s" % (command, args)) + + my_system_call.__doc__ = "Calls %s" % command return my_system_call #------------------------------------------------------------------------------- @@ -62,13 +64,25 @@ class PrefilterFrontEnd(LineFrontEndBase): debug = False - def __init__(self, ipython0=None, *args, **kwargs): + def __init__(self, ipython0=None, argv=None, *args, **kwargs): """ Parameters: ----------- ipython0: an optional ipython0 instance to use for command prefiltering and completion. + + argv : list, optional + Used as the instance's argv value. If not given, [] is used. """ + if argv is None: + argv = [] + # This is a hack to avoid the IPython exception hook to trigger + # on exceptions (https://bugs.launchpad.net/bugs/337105) + # XXX: This is horrible: module-leve monkey patching -> side + # effects. + from IPython import iplib + iplib.InteractiveShell.isthreaded = True + LineFrontEndBase.__init__(self, *args, **kwargs) self.shell.output_trap = RedirectorOutputTrap( out_callback=self.write, @@ -83,10 +97,16 @@ class PrefilterFrontEnd(LineFrontEndBase): if ipython0 is None: # Instanciate an IPython0 interpreter to be able to use the # prefiltering. + # Suppress all key input, to avoid waiting + def my_rawinput(x=None): + return '\n' + old_rawinput = __builtin__.raw_input + __builtin__.raw_input = my_rawinput # XXX: argv=[] is a bit bold. - ipython0 = make_IPython(argv=[], + ipython0 = make_IPython(argv=argv, user_ns=self.shell.user_ns, user_global_ns=self.shell.user_global_ns) + __builtin__.raw_input = old_rawinput self.ipython0 = ipython0 # Set the pager: self.ipython0.set_hook('show_in_pager', @@ -98,16 +118,17 @@ class PrefilterFrontEnd(LineFrontEndBase): self._ip.system = self.system_call # XXX: Muck around with magics so that they work better # in our environment - self.ipython0.magic_ls = mk_system_call(self.system_call, - 'ls -CF') + if not sys.platform.startswith('win'): + self.ipython0.magic_ls = mk_system_call(self.system_call, + 'ls -CF') # And now clean up the mess created by ipython0 self.release_output() if not 'banner' in kwargs and self.banner is None: - self.banner = self.ipython0.BANNER + """ -This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code.""" + self.banner = self.ipython0.BANNER + # FIXME: __init__ and start should be two different steps self.start() #-------------------------------------------------------------------------- @@ -117,7 +138,10 @@ This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code.""" def show_traceback(self): """ Use ipython0 to capture the last traceback and display it. """ - self.capture_output() + # Don't do the capture; the except_hook has already done some + # modifications to the IO streams, if we store them, we'll be + # storing the wrong ones. + #self.capture_output() self.ipython0.showtraceback(tb_offset=-1) self.release_output() @@ -171,7 +195,7 @@ This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code.""" def complete(self, line): # FIXME: This should be factored out in the linefrontendbase # method. - word = line.split('\n')[-1].split(' ')[-1] + word = self._get_completion_text(line) completions = self.ipython0.complete(word) # FIXME: The proper sort should be done in the complete method. key = lambda x: x.replace('_', '') @@ -244,3 +268,18 @@ This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code.""" """ self.ipython0.atexit_operations() + + def _get_completion_text(self, line): + """ Returns the text to be completed by breaking the line at specified + delimiters. + """ + # Break at: spaces, '=', all parentheses (except if balanced). + # FIXME2: In the future, we need to make the implementation similar to + # that in the 'pyreadline' module (modes/basemode.py) where we break at + # each delimiter and try to complete the residual line, until we get a + # successful list of completions. + expression = '\s|=|,|:|\((?!.*\))|\[(?!.*\])|\{(?!.*\})' + complete_sep = re.compile(expression) + text = complete_sep.split(line)[-1] + return text + diff --git a/IPython/frontend/_process/__init__.py b/IPython/frontend/process/__init__.py similarity index 100% rename from IPython/frontend/_process/__init__.py rename to IPython/frontend/process/__init__.py diff --git a/IPython/frontend/_process/killableprocess.py b/IPython/frontend/process/killableprocess.py similarity index 96% rename from IPython/frontend/_process/killableprocess.py rename to IPython/frontend/process/killableprocess.py index 955482c..399bd70 100644 --- a/IPython/frontend/_process/killableprocess.py +++ b/IPython/frontend/process/killableprocess.py @@ -151,7 +151,12 @@ else: self._thread = ht self.pid = pid - winprocess.AssignProcessToJobObject(self._job, hp) + # XXX: A try/except to fix UAC-related problems under + # Windows Vista, when reparenting jobs. + try: + winprocess.AssignProcessToJobObject(self._job, hp) + except WindowsError: + pass winprocess.ResumeThread(ht) if p2cread is not None: diff --git a/IPython/frontend/_process/pipedprocess.py b/IPython/frontend/process/pipedprocess.py similarity index 100% rename from IPython/frontend/_process/pipedprocess.py rename to IPython/frontend/process/pipedprocess.py diff --git a/IPython/frontend/_process/winprocess.py b/IPython/frontend/process/winprocess.py similarity index 100% rename from IPython/frontend/_process/winprocess.py rename to IPython/frontend/process/winprocess.py diff --git a/IPython/frontend/tests/test_asyncfrontendbase.py b/IPython/frontend/tests/test_asyncfrontendbase.py index 617456e..fb497c8 100644 --- a/IPython/frontend/tests/test_asyncfrontendbase.py +++ b/IPython/frontend/tests/test_asyncfrontendbase.py @@ -1,6 +1,6 @@ # encoding: utf-8 -"""This file contains unittests for the frontendbase module.""" +"""This file contains unittests for the asyncfrontendbase module.""" __docformat__ = "restructuredtext en" @@ -15,17 +15,15 @@ __docformat__ = "restructuredtext en" # Imports #--------------------------------------------------------------------------- -import unittest +# Tell nose to skip this module +__test__ = {} -try: - from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase - from IPython.frontend import frontendbase - from IPython.kernel.engineservice import EngineService -except ImportError: - import nose - raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap") +from twisted.trial import unittest +from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase +from IPython.frontend import frontendbase +from IPython.kernel.engineservice import EngineService +from IPython.testing.parametric import Parametric, parametric -from IPython.testing.decorators import skip class FrontEndCallbackChecker(AsyncFrontEndBase): """FrontEndBase subclass for checking callbacks""" @@ -44,14 +42,11 @@ class FrontEndCallbackChecker(AsyncFrontEndBase): self.renderResultCalled = True return result - def render_error(self, failure): self.renderErrorCalled = True return failure - - class TestAsyncFrontendBase(unittest.TestCase): def setUp(self): """Setup the EngineService and FrontEndBase""" @@ -59,97 +54,56 @@ class TestAsyncFrontendBase(unittest.TestCase): self.fb = FrontEndCallbackChecker(engine=EngineService()) def test_implements_IFrontEnd(self): - assert(frontendbase.IFrontEnd.implementedBy( + self.assert_(frontendbase.IFrontEnd.implementedBy( AsyncFrontEndBase)) def test_is_complete_returns_False_for_incomplete_block(self): - """""" - block = """def test(a):""" - - assert(self.fb.is_complete(block) == False) + self.assert_(self.fb.is_complete(block) == False) def test_is_complete_returns_True_for_complete_block(self): - """""" - block = """def test(a): pass""" - - assert(self.fb.is_complete(block)) - + self.assert_(self.fb.is_complete(block)) block = """a=3""" - - assert(self.fb.is_complete(block)) + self.assert_(self.fb.is_complete(block)) def test_blockID_added_to_result(self): block = """3+3""" - d = self.fb.execute(block, blockID='TEST_ID') - - d.addCallback(self.checkBlockID, expected='TEST_ID') + d.addCallback(lambda r: self.assert_(r['blockID']=='TEST_ID')) + return d def test_blockID_added_to_failure(self): block = "raise Exception()" - d = self.fb.execute(block,blockID='TEST_ID') - d.addErrback(self.checkFailureID, expected='TEST_ID') - - def checkBlockID(self, result, expected=""): - assert(result['blockID'] == expected) - - - def checkFailureID(self, failure, expected=""): - assert(failure.blockID == expected) - + d.addErrback(lambda f: self.assert_(f.blockID=='TEST_ID')) + return d def test_callbacks_added_to_execute(self): - """test that - update_cell_prompt - render_result - - are added to execute request - """ - d = self.fb.execute("10+10") - d.addCallback(self.checkCallbacks) + d.addCallback(lambda r: self.assert_(self.fb.updateCalled and self.fb.renderResultCalled)) + return d - def checkCallbacks(self, result): - assert(self.fb.updateCalled) - assert(self.fb.renderResultCalled) - - @skip("This test fails and lead to an unhandled error in a Deferred.") def test_error_callback_added_to_execute(self): - """test that render_error called on execution error""" + """Test that render_error called on execution error.""" d = self.fb.execute("raise Exception()") - d.addCallback(self.checkRenderError) - - def checkRenderError(self, result): - assert(self.fb.renderErrorCalled) + d.addErrback(lambda f: self.assert_(self.fb.renderErrorCalled)) + return d def test_history_returns_expected_block(self): - """Make sure history browsing doesn't fail""" + """Make sure history browsing doesn't fail.""" blocks = ["a=1","a=2","a=3"] - for b in blocks: - d = self.fb.execute(b) - - # d is now the deferred for the last executed block - d.addCallback(self.historyTests, blocks) - - - def historyTests(self, result, blocks): - """historyTests""" - - assert(len(blocks) >= 3) - assert(self.fb.get_history_previous("") == blocks[-2]) - assert(self.fb.get_history_previous("") == blocks[-3]) - assert(self.fb.get_history_next() == blocks[-2]) - - - def test_history_returns_none_at_startup(self): - """test_history_returns_none_at_startup""" - - assert(self.fb.get_history_previous("")==None) - assert(self.fb.get_history_next()==None) - - + d = self.fb.execute(blocks[0]) + d.addCallback(lambda _: self.fb.execute(blocks[1])) + d.addCallback(lambda _: self.fb.execute(blocks[2])) + d.addCallback(lambda _: self.assert_(self.fb.get_history_previous("")==blocks[-2])) + d.addCallback(lambda _: self.assert_(self.fb.get_history_previous("")==blocks[-3])) + d.addCallback(lambda _: self.assert_(self.fb.get_history_next()==blocks[-2])) + return d + + def test_history_returns_none_at_startup(self): + self.assert_(self.fb.get_history_previous("")==None) + self.assert_(self.fb.get_history_next()==None) + diff --git a/IPython/frontend/tests/test_linefrontend.py b/IPython/frontend/tests/test_linefrontend.py new file mode 100644 index 0000000..d0d53ac --- /dev/null +++ b/IPython/frontend/tests/test_linefrontend.py @@ -0,0 +1,37 @@ +# encoding: utf-8 +""" +Test the LineFrontEnd +""" + +__docformat__ = "restructuredtext en" + +#------------------------------------------------------------------------------- +# Copyright (C) 2008 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. +#------------------------------------------------------------------------------- + +from IPython.frontend.linefrontendbase import LineFrontEndBase +from copy import deepcopy +import nose.tools as nt + +class ConcreteLineFrontEnd(LineFrontEndBase): + """ A concrete class to test the LineFrontEndBase. + """ + def capture_output(self): + pass + + def release_output(self): + pass + + +def test_is_complete(): + """ Tests line completion heuristic. + """ + frontend = ConcreteLineFrontEnd() + yield nt.assert_true, not frontend.is_complete('for x in \\') + yield nt.assert_true, not frontend.is_complete('for x in (1, ):') + yield nt.assert_true, frontend.is_complete('for x in (1, ):\n pass') + + diff --git a/IPython/frontend/tests/test_prefilterfrontend.py b/IPython/frontend/tests/test_prefilterfrontend.py index 665a2c5..317f1ed 100644 --- a/IPython/frontend/tests/test_prefilterfrontend.py +++ b/IPython/frontend/tests/test_prefilterfrontend.py @@ -12,12 +12,32 @@ __docformat__ = "restructuredtext en" # in the file COPYING, distributed as part of this software. #------------------------------------------------------------------------------- +from copy import copy, deepcopy from cStringIO import StringIO import string -from IPython.ipapi import get as get_ipython0 +from nose.tools import assert_equal + from IPython.frontend.prefilterfrontend import PrefilterFrontEnd -from copy import deepcopy +from IPython.ipapi import get as get_ipython0 +from IPython.testing.plugin.ipdoctest import default_argv + + +def safe_deepcopy(d): + """ Deep copy every key of the given dict, when possible. Elsewhere + do a copy. + """ + copied_d = dict() + for key, value in d.iteritems(): + try: + copied_d[key] = deepcopy(value) + except: + try: + copied_d[key] = copy(value) + except: + copied_d[key] = value + return copied_d + class TestPrefilterFrontEnd(PrefilterFrontEnd): @@ -26,16 +46,8 @@ class TestPrefilterFrontEnd(PrefilterFrontEnd): banner = '' def __init__(self): - ipython0 = get_ipython0().IP self.out = StringIO() - PrefilterFrontEnd.__init__(self, ipython0=ipython0) - # Clean up the namespace for isolation between tests - user_ns = self.ipython0.user_ns - # We need to keep references to things so that they don't - # get garbage collected (this stinks). - self.shadow_ns = dict() - for i in self.ipython0.magic_who_ls(): - self.shadow_ns[i] = user_ns.pop(i) + PrefilterFrontEnd.__init__(self,argv=default_argv()) # Some more code for isolation (yeah, crazy) self._on_enter() self.out.flush() @@ -52,17 +64,31 @@ class TestPrefilterFrontEnd(PrefilterFrontEnd): def isolate_ipython0(func): """ Decorator to isolate execution that involves an iptyhon0. + + Notes + ----- + + Apply only to functions with no arguments. Nose skips functions + with arguments. """ - def my_func(*args, **kwargs): - ipython0 = get_ipython0().IP - user_ns = deepcopy(ipython0.user_ns) - global_ns = deepcopy(ipython0.global_ns) + def my_func(): + iplib = get_ipython0() + if iplib is None: + return func() + ipython0 = iplib.IP + global_ns = safe_deepcopy(ipython0.user_global_ns) + user_ns = safe_deepcopy(ipython0.user_ns) try: - func(*args, **kwargs) + out = func() finally: ipython0.user_ns = user_ns - ipython0.global_ns = global_ns + ipython0.user_global_ns = global_ns + # Undo the hack at creation of PrefilterFrontEnd + from IPython import iplib + iplib.InteractiveShell.isthreaded = False + return out + my_func.__name__ = func.__name__ return my_func @@ -74,7 +100,7 @@ def test_execution(): f.input_buffer = 'print 1' f._on_enter() out_value = f.out.getvalue() - assert out_value == '1\n' + assert_equal(out_value, '1\n') @isolate_ipython0 @@ -87,20 +113,20 @@ def test_multiline(): f.input_buffer += 'print 1' f._on_enter() out_value = f.out.getvalue() - assert out_value == '' + yield assert_equal, out_value, '' f._on_enter() out_value = f.out.getvalue() - assert out_value == '1\n' + yield assert_equal, out_value, '1\n' f = TestPrefilterFrontEnd() f.input_buffer='(1 +' f._on_enter() f.input_buffer += '0)' f._on_enter() out_value = f.out.getvalue() - assert out_value == '' + yield assert_equal, out_value, '' f._on_enter() out_value = f.out.getvalue() - assert out_value == '1\n' + yield assert_equal, out_value, '1\n' @isolate_ipython0 @@ -113,13 +139,13 @@ def test_capture(): 'import os; out=os.fdopen(1, "w"); out.write("1") ; out.flush()' f._on_enter() out_value = f.out.getvalue() - assert out_value == '1' + yield assert_equal, out_value, '1' f = TestPrefilterFrontEnd() f.input_buffer = \ 'import os; out=os.fdopen(2, "w"); out.write("1") ; out.flush()' f._on_enter() out_value = f.out.getvalue() - assert out_value == '1' + yield assert_equal, out_value, '1' @isolate_ipython0 @@ -129,10 +155,16 @@ def test_magic(): This test is fairly fragile and will break when magics change. """ f = TestPrefilterFrontEnd() + # Before checking the interactive namespace, make sure it's clear (it can + # otherwise pick up things stored in the user's local db) + f.input_buffer += '%reset -f' + f._on_enter() + f.complete_current_input() + # Now, run the %who magic and check output f.input_buffer += '%who' f._on_enter() out_value = f.out.getvalue() - assert out_value == 'Interactive namespace is empty.\n' + assert_equal(out_value, 'Interactive namespace is empty.\n') @isolate_ipython0 @@ -156,8 +188,8 @@ def test_help(): @isolate_ipython0 -def test_completion(): - """ Test command-line completion. +def test_completion_simple(): + """ Test command-line completion on trivial examples. """ f = TestPrefilterFrontEnd() f.input_buffer = 'zzza = 1' @@ -167,8 +199,47 @@ def test_completion(): f.input_buffer = 'zz' f.complete_current_input() out_value = f.out.getvalue() - assert out_value == '\nzzza zzzb ' - assert f.input_buffer == 'zzz' + yield assert_equal, out_value, '\nzzza zzzb ' + yield assert_equal, f.input_buffer, 'zzz' + + +@isolate_ipython0 +def test_completion_parenthesis(): + """ Test command-line completion when a parenthesis is open. + """ + f = TestPrefilterFrontEnd() + f.input_buffer = 'zzza = 1' + f._on_enter() + f.input_buffer = 'zzzb = 2' + f._on_enter() + f.input_buffer = 'map(zz' + f.complete_current_input() + out_value = f.out.getvalue() + yield assert_equal, out_value, '\nzzza zzzb ' + yield assert_equal, f.input_buffer, 'map(zzz' + + +@isolate_ipython0 +def test_completion_indexing(): + """ Test command-line completion when indexing on objects. + """ + f = TestPrefilterFrontEnd() + f.input_buffer = 'a = [0]' + f._on_enter() + f.input_buffer = 'a[0].' + f.complete_current_input() + assert_equal(f.input_buffer, 'a[0].__') + + +@isolate_ipython0 +def test_completion_equal(): + """ Test command-line completion when the delimiter is "=", not " ". + """ + f = TestPrefilterFrontEnd() + f.input_buffer = 'a=1.' + f.complete_current_input() + assert_equal(f.input_buffer, 'a=1.__') + if __name__ == '__main__': @@ -177,4 +248,5 @@ if __name__ == '__main__': test_execution() test_multiline() test_capture() - test_completion() + test_completion_simple() + test_completion_complex() diff --git a/IPython/frontend/tests/test_process.py b/IPython/frontend/tests/test_process.py index dc8db5f..0b7adf8 100644 --- a/IPython/frontend/tests/test_process.py +++ b/IPython/frontend/tests/test_process.py @@ -5,18 +5,18 @@ Test process execution and IO redirection. __docformat__ = "restructuredtext en" -#------------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 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. -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- from cStringIO import StringIO from time import sleep import sys -from IPython.frontend._process import PipedProcess +from IPython.frontend.process import PipedProcess from IPython.testing import decorators as testdec diff --git a/IPython/frontend/wx/console_widget.py b/IPython/frontend/wx/console_widget.py index 30ec0b8..8fcb17a 100644 --- a/IPython/frontend/wx/console_widget.py +++ b/IPython/frontend/wx/console_widget.py @@ -25,6 +25,8 @@ import wx.stc as stc from wx.py import editwindow import time import sys +import string + LINESEP = '\n' if sys.platform == 'win32': LINESEP = '\n\r' @@ -33,20 +35,26 @@ import re # FIXME: Need to provide an API for non user-generated display on the # screen: this should not be editable by the user. +#------------------------------------------------------------------------------- +# Constants +#------------------------------------------------------------------------------- +_COMPLETE_BUFFER_MARKER = 31 +_ERROR_MARKER = 30 +_INPUT_MARKER = 29 _DEFAULT_SIZE = 10 if sys.platform == 'darwin': _DEFAULT_SIZE = 12 _DEFAULT_STYLE = { - 'stdout' : 'fore:#0000FF', - 'stderr' : 'fore:#007f00', - 'trace' : 'fore:#FF0000', - + #background definition 'default' : 'size:%d' % _DEFAULT_SIZE, 'bracegood' : 'fore:#00AA00,back:#000000,bold', 'bracebad' : 'fore:#FF0000,back:#000000,bold', + # Edge column: a number of None + 'edge_column' : -1, + # properties for the various Python lexer styles 'comment' : 'fore:#007F00', 'number' : 'fore:#007F7F', @@ -57,7 +65,24 @@ _DEFAULT_STYLE = { 'tripledouble' : 'fore:#7F0000', 'class' : 'fore:#0000FF,bold,underline', 'def' : 'fore:#007F7F,bold', - 'operator' : 'bold' + 'operator' : 'bold', + + # Default colors + 'trace' : '#FAFAF1', # Nice green + 'stdout' : '#FDFFD3', # Nice yellow + 'stderr' : '#FFF1F1', # Nice red + + # Default scintilla settings + 'antialiasing' : True, + 'carret_color' : 'BLACK', + 'background_color' :'WHITE', + + #prompt definition + 'prompt_in1' : \ + '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02', + + 'prompt_out': \ + '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02', } # new style numbers @@ -69,6 +94,47 @@ _TRACE_STYLE = 17 # system colors #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND) +# Translation table from ANSI escape sequences to color. +ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'], + '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'], + '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'], + '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'], + '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'], + '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'], + '1;34': [12, 'LIGHT BLUE'], '1;35': + [13, 'MEDIUM VIOLET RED'], + '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']} + +# XXX: Maybe one day we should factor this code with ColorANSI. Right now +# ColorANSI is hard to reuse and makes our code more complex. + +#we define platform specific fonts +if wx.Platform == '__WXMSW__': + FACES = { 'times': 'Times New Roman', + 'mono' : 'Courier New', + 'helv' : 'Arial', + 'other': 'Comic Sans MS', + 'size' : 10, + 'size2': 8, + } +elif wx.Platform == '__WXMAC__': + FACES = { 'times': 'Times New Roman', + 'mono' : 'Monaco', + 'helv' : 'Arial', + 'other': 'Comic Sans MS', + 'size' : 10, + 'size2': 8, + } +else: + FACES = { 'times': 'Times', + 'mono' : 'Courier', + 'helv' : 'Helvetica', + 'other': 'new century schoolbook', + 'size' : 10, + 'size2': 8, + } + + #------------------------------------------------------------------------------- # The console widget class #------------------------------------------------------------------------------- @@ -83,6 +149,9 @@ class ConsoleWidget(editwindow.EditWindow): # stored. title = 'Console' + # Last prompt printed + last_prompt = '' + # The buffer being edited. def _set_input_buffer(self, string): self.SetSelection(self.current_prompt_pos, self.GetLength()) @@ -103,19 +172,11 @@ class ConsoleWidget(editwindow.EditWindow): # Translation table from ANSI escape sequences to color. Override # this to specify your colors. - ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'], - '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'], - '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'], - '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'], - '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'], - '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'], - '1;34': [12, 'LIGHT BLUE'], '1;35': - [13, 'MEDIUM VIOLET RED'], - '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']} - - # The color of the carret (call _apply_style() after setting) - carret_color = 'BLACK' + ANSI_STYLES = ANSI_STYLES.copy() + # Font faces + faces = FACES.copy() + # Store the last time a refresh was done _last_refresh_time = 0 @@ -126,7 +187,11 @@ class ConsoleWidget(editwindow.EditWindow): def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.WANTS_CHARS, ): editwindow.EditWindow.__init__(self, parent, id, pos, size, style) - self._configure_scintilla() + self.configure_scintilla() + # Track if 'enter' key as ever been processed + # This variable will only be reallowed until key goes up + self.enter_catched = False + self.current_prompt_pos = 0 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down) self.Bind(wx.EVT_KEY_UP, self._on_key_up) @@ -193,8 +258,19 @@ class ConsoleWidget(editwindow.EditWindow): self.current_prompt_pos = self.GetLength() self.current_prompt_line = self.GetCurrentLine() self.EnsureCaretVisible() + self.last_prompt = prompt + def continuation_prompt(self): + """ Returns the current continuation prompt. + We need to implement this method here to deal with the + ascii escape sequences cleaning up. + """ + # ASCII-less prompt + ascii_less = ''.join(self.color_pat.split(self.last_prompt)[2::2]) + return "."*(len(ascii_less)-2) + ': ' + + def scroll_to_bottom(self): maxrange = self.GetScrollRange(wx.VERTICAL) self.ScrollLines(maxrange) @@ -216,37 +292,24 @@ class ConsoleWidget(editwindow.EditWindow): """ return self.GetSize()[0]/self.GetCharWidth() - #-------------------------------------------------------------------------- - # EditWindow API - #-------------------------------------------------------------------------- - def OnUpdateUI(self, event): - """ Override the OnUpdateUI of the EditWindow class, to prevent - syntax highlighting both for faster redraw, and for more - consistent look and feel. + def configure_scintilla(self): + """ Set up all the styling option of the embedded scintilla + widget. """ + p = self.style.copy() + + # Marker for complete buffer. + self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND, + background=p['trace']) - #-------------------------------------------------------------------------- - # Private API - #-------------------------------------------------------------------------- - - def _apply_style(self): - """ Applies the colors for the different text elements and the - carret. - """ - self.SetCaretForeground(self.carret_color) - - #self.StyleClearAll() - self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, - "fore:#FF0000,back:#0000FF,bold") - self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, - "fore:#000000,back:#FF0000,bold") - - for style in self.ANSI_STYLES.values(): - self.StyleSetSpec(style[0], "bold,fore:%s" % style[1]) + # Marker for current input buffer. + self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND, + background=p['stdout']) + # Marker for tracebacks. + self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND, + background=p['stderr']) - - def _configure_scintilla(self): self.SetEOLMode(stc.STC_EOL_LF) # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside @@ -268,7 +331,9 @@ class ConsoleWidget(editwindow.EditWindow): self.SetWrapMode(stc.STC_WRAP_CHAR) self.SetWrapMode(stc.STC_WRAP_WORD) self.SetBufferedDraw(True) - self.SetUseAntiAliasing(True) + + self.SetUseAntiAliasing(p['antialiasing']) + self.SetLayoutCache(stc.STC_CACHE_PAGE) self.SetUndoCollection(False) self.SetUseTabs(True) @@ -289,23 +354,48 @@ class ConsoleWidget(editwindow.EditWindow): self.SetMarginWidth(1, 0) self.SetMarginWidth(2, 0) - self._apply_style() - # Xterm escape sequences self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?') self.title_pat = re.compile('\x1b]0;(.*?)\x07') - #self.SetEdgeMode(stc.STC_EDGE_LINE) - #self.SetEdgeColumn(80) - # styles - p = self.style - self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default']) + + self.SetCaretForeground(p['carret_color']) + + background_color = p['background_color'] + + if 'default' in p: + if 'back' not in p['default']: + p['default'] += ',back:%s' % background_color + if 'size' not in p['default']: + p['default'] += ',size:%s' % self.faces['size'] + if 'face' not in p['default']: + p['default'] += ',face:%s' % self.faces['mono'] + + self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default']) + else: + self.StyleSetSpec(stc.STC_STYLE_DEFAULT, + "fore:%s,back:%s,size:%d,face:%s" + % (self.ANSI_STYLES['0;30'][1], + background_color, + self.faces['size'], self.faces['mono'])) + self.StyleClearAll() + + # XXX: two lines below are usefull if not using the lexer + #for style in self.ANSI_STYLES.values(): + # self.StyleSetSpec(style[0], "bold,fore:%s" % style[1]) + + # prompt definition + self.prompt_in1 = p['prompt_in1'] + self.prompt_out = p['prompt_out'] + + self.output_prompt_template = string.Template(self.prompt_out) + self.input_prompt_template = string.Template(self.prompt_in1) + self.StyleSetSpec(_STDOUT_STYLE, p['stdout']) self.StyleSetSpec(_STDERR_STYLE, p['stderr']) self.StyleSetSpec(_TRACE_STYLE, p['trace']) - self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood']) self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad']) self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment']) @@ -321,6 +411,28 @@ class ConsoleWidget(editwindow.EditWindow): self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator']) self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment']) + edge_column = p['edge_column'] + if edge_column is not None and edge_column > 0: + #we add a vertical line to console widget + self.SetEdgeMode(stc.STC_EDGE_LINE) + self.SetEdgeColumn(edge_column) + + + #-------------------------------------------------------------------------- + # EditWindow API + #-------------------------------------------------------------------------- + + def OnUpdateUI(self, event): + """ Override the OnUpdateUI of the EditWindow class, to prevent + syntax highlighting both for faster redraw, and for more + consistent look and feel. + """ + + + #-------------------------------------------------------------------------- + # Private API + #-------------------------------------------------------------------------- + def _on_key_down(self, event, skip=True): """ Key press callback used for correcting behavior for console-like interfaces: the cursor is constraint to be after @@ -329,6 +441,11 @@ class ConsoleWidget(editwindow.EditWindow): Return True if event as been catched. """ catched = True + # XXX: Would the right way to do this be to have a + # dictionary at the instance level associating keys with + # callbacks? How would we deal with inheritance? And Do the + # different callbacks share local variables? + # Intercept some specific keys. if event.KeyCode == ord('L') and event.ControlDown() : self.scroll_to_bottom() @@ -346,6 +463,10 @@ class ConsoleWidget(editwindow.EditWindow): self.ScrollPages(-1) elif event.KeyCode == wx.WXK_PAGEDOWN: self.ScrollPages(1) + elif event.KeyCode == wx.WXK_HOME: + self.GotoPos(self.GetLength()) + elif event.KeyCode == wx.WXK_END: + self.GotoPos(self.GetLength()) elif event.KeyCode == wx.WXK_UP and event.ShiftDown(): self.ScrollLines(-1) elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown(): @@ -357,16 +478,20 @@ class ConsoleWidget(editwindow.EditWindow): event.Skip() else: if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \ - event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN): + event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN, + wx.MOD_SHIFT): catched = True - self.CallTipCancel() - self.write('\n', refresh=False) - # Under windows scintilla seems to be doing funny stuff to the - # line returns here, but the getter for input_buffer filters - # this out. - if sys.platform == 'win32': - self.input_buffer = self.input_buffer - self._on_enter() + if not self.enter_catched: + self.CallTipCancel() + if event.Modifiers == wx.MOD_SHIFT: + # Try to force execution + self.GotoPos(self.GetLength()) + self.write('\n' + self.continuation_prompt(), + refresh=False) + self._on_enter() + else: + self._on_enter() + self.enter_catched = True elif event.KeyCode == wx.WXK_HOME: if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN): @@ -391,16 +516,28 @@ class ConsoleWidget(editwindow.EditWindow): catched = True elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK): - if self.GetCurrentPos() > self.current_prompt_pos: + if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1): + event.Skip() + catched = True + + elif event.KeyCode == wx.WXK_RIGHT: + if not self._keep_cursor_in_buffer(self.GetCurrentPos() + 1): + event.Skip() + catched = True + + + elif event.KeyCode == wx.WXK_DELETE: + if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1): event.Skip() catched = True if skip and not catched: # Put the cursor back in the edit region - if self.GetCurrentPos() < self.current_prompt_pos: - self.GotoPos(self.current_prompt_pos) - else: - event.Skip() + if not self._keep_cursor_in_buffer(): + if not (self.GetCurrentPos() == self.GetLength() + and event.KeyCode == wx.WXK_DELETE): + event.Skip() + catched = True return catched @@ -408,17 +545,69 @@ class ConsoleWidget(editwindow.EditWindow): def _on_key_up(self, event, skip=True): """ If cursor is outside the editing region, put it back. """ - event.Skip() - if self.GetCurrentPos() < self.current_prompt_pos: - self.GotoPos(self.current_prompt_pos) + if skip: + event.Skip() + self._keep_cursor_in_buffer() + + + # XXX: I need to avoid the problem of having an empty glass; + def _keep_cursor_in_buffer(self, pos=None): + """ Checks if the cursor is where it is allowed to be. If not, + put it back. + Returns + ------- + cursor_moved: Boolean + whether or not the cursor was moved by this routine. + + Notes + ------ + WARNING: This does proper checks only for horizontal + movements. + """ + if pos is None: + current_pos = self.GetCurrentPos() + else: + current_pos = pos + if current_pos < self.current_prompt_pos: + self.GotoPos(self.current_prompt_pos) + return True + line_num = self.LineFromPosition(current_pos) + if not current_pos > self.GetLength(): + line_pos = self.GetColumn(current_pos) + else: + line_pos = self.GetColumn(self.GetLength()) + line = self.GetLine(line_num) + # Jump the continuation prompt + continuation_prompt = self.continuation_prompt() + if ( line.startswith(continuation_prompt) + and line_pos < len(continuation_prompt)): + if line_pos < 2: + # We are at the beginning of the line, trying to move + # forward: jump forward. + self.GotoPos(current_pos + 1 + + len(continuation_prompt) - line_pos) + else: + # Jump back up + self.GotoPos(self.GetLineEndPosition(line_num-1)) + return True + elif ( current_pos > self.GetLineEndPosition(line_num) + and not current_pos == self.GetLength()): + # Jump to next line + self.GotoPos(current_pos + 1 + + len(continuation_prompt)) + return True + + # We re-allow enter event processing + self.enter_catched = False + return False if __name__ == '__main__': # Some simple code to test the console widget. class MainWindow(wx.Frame): def __init__(self, parent, id, title): - wx.Frame.__init__(self, parent, id, title, size=(300,250)) + wx.Frame.__init__(self, parent, id, title, size=(300, 250)) self._sizer = wx.BoxSizer(wx.VERTICAL) self.console_widget = ConsoleWidget(self) self._sizer.Add(self.console_widget, 1, wx.EXPAND) diff --git a/IPython/frontend/wx/ipythonx.py b/IPython/frontend/wx/ipythonx.py index df81443..af7d322 100644 --- a/IPython/frontend/wx/ipythonx.py +++ b/IPython/frontend/wx/ipythonx.py @@ -80,6 +80,15 @@ class IPythonX(wx.Frame): self.SetSizer(self._sizer) self.SetAutoLayout(1) self.Show(True) + wx.EVT_CLOSE(self, self.on_close) + + + def on_close(self, event): + """ Called on closing the windows. + + Stops the event loop, to close all the child windows. + """ + wx.CallAfter(wx.Exit) def main(): diff --git a/IPython/frontend/wx/wx_frontend.py b/IPython/frontend/wx/wx_frontend.py index d4182cc..854c47e 100644 --- a/IPython/frontend/wx/wx_frontend.py +++ b/IPython/frontend/wx/wx_frontend.py @@ -25,38 +25,19 @@ __docformat__ = "restructuredtext en" # Major library imports import re import __builtin__ -from time import sleep import sys from threading import Lock -import string import wx from wx import stc # Ipython-specific imports. -from IPython.frontend._process import PipedProcess -from console_widget import ConsoleWidget +from IPython.frontend.process import PipedProcess +from console_widget import ConsoleWidget, _COMPLETE_BUFFER_MARKER, \ + _ERROR_MARKER, _INPUT_MARKER from IPython.frontend.prefilterfrontend import PrefilterFrontEnd #------------------------------------------------------------------------------- -# Constants -#------------------------------------------------------------------------------- - -_COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green -_INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow -_ERROR_BG = '#FFF1F1' # Nice red - -_COMPLETE_BUFFER_MARKER = 31 -_ERROR_MARKER = 30 -_INPUT_MARKER = 29 - -prompt_in1 = \ - '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02' - -prompt_out = \ - '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02' - -#------------------------------------------------------------------------------- # Classes to implement the Wx frontend #------------------------------------------------------------------------------- class WxController(ConsoleWidget, PrefilterFrontEnd): @@ -66,11 +47,7 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): This class inherits from ConsoleWidget, that provides a console-like widget to provide a text-rendering widget suitable for a terminal. """ - - output_prompt_template = string.Template(prompt_out) - - input_prompt_template = string.Template(prompt_in1) - + # Print debug info on what is happening to the console. debug = False @@ -138,25 +115,24 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.CLIP_CHILDREN|wx.WANTS_CHARS, + styledef=None, *args, **kwds): """ Create Shell instance. + + Parameters + ----------- + styledef : dict, optional + styledef is the dictionary of options used to define the + style. """ + if styledef is not None: + self.style = styledef ConsoleWidget.__init__(self, parent, id, pos, size, style) PrefilterFrontEnd.__init__(self, **kwds) # Stick in our own raw_input: self.ipython0.raw_input = self.raw_input - # Marker for complete buffer. - self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND, - background=_COMPLETE_BUFFER_BG) - # Marker for current input buffer. - self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND, - background=_INPUT_BUFFER_BG) - # Marker for tracebacks. - self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND, - background=_ERROR_BG) - # A time for flushing the write buffer BUFFER_FLUSH_TIMER_ID = 100 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID) @@ -171,8 +147,7 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): self.shell.user_ns['self'] = self # Inject our own raw_input in namespace self.shell.user_ns['raw_input'] = self.raw_input - - + def raw_input(self, prompt=''): """ A replacement from python's raw_input. """ @@ -251,11 +226,8 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): if (self.AutoCompActive() and line and not line[-1] == '.') \ or create==True: suggestion, completions = self.complete(line) - offset=0 if completions: - complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]') - residual = complete_sep.split(line)[-1] - offset = len(residual) + offset = len(self._get_completion_text(line)) self.pop_completion(completions, offset=offset) if self.debug: print >>sys.__stdout__, completions @@ -276,6 +248,14 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): milliseconds=100, oneShot=True) + def clear_screen(self): + """ Empty completely the widget. + """ + self.ClearAll() + self.new_prompt(self.input_prompt_template.substitute( + number=(self.last_result['number'] + 1))) + + #-------------------------------------------------------------------------- # LineFrontEnd interface #-------------------------------------------------------------------------- @@ -299,6 +279,41 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): raw_string=raw_string) wx.CallAfter(callback) + + def execute_command(self, command, hidden=False): + """ Execute a command, not only in the model, but also in the + view. + """ + # XXX: This method needs to be integrated in the base fronted + # interface + if hidden: + return self.shell.execute(command) + else: + # XXX: we are not storing the input buffer previous to the + # execution, as this forces us to run the execution + # input_buffer a yield, which is not good. + ##current_buffer = self.shell.control.input_buffer + command = command.rstrip() + if len(command.split('\n')) > 1: + # The input command is several lines long, we need to + # force the execution to happen + command += '\n' + cleaned_command = self.prefilter_input(command) + self.input_buffer = command + # Do not use wx.Yield() (aka GUI.process_events()) to avoid + # recursive yields. + self.ProcessEvent(wx.PaintEvent()) + self.write('\n') + if not self.is_complete(cleaned_command + '\n'): + self._colorize_input_buffer() + self.render_error('Incomplete or invalid input') + self.new_prompt(self.input_prompt_template.substitute( + number=(self.last_result['number'] + 1))) + return False + self._on_enter() + return True + + def save_output_hooks(self): self.__old_raw_input = __builtin__.raw_input PrefilterFrontEnd.save_output_hooks(self) @@ -356,10 +371,16 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER) + def continuation_prompt(self, *args, **kwargs): + # Avoid multiple inheritence, be explicit about which + # parent method class gets called + return ConsoleWidget.continuation_prompt(self, *args, **kwargs) + + def write(self, *args, **kwargs): # Avoid multiple inheritence, be explicit about which # parent method class gets called - ConsoleWidget.write(self, *args, **kwargs) + return ConsoleWidget.write(self, *args, **kwargs) def _on_key_down(self, event, skip=True): @@ -367,7 +388,7 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): widget handle them, and put our logic afterward. """ # FIXME: This method needs to be broken down in smaller ones. - current_line_number = self.GetCurrentLine() + current_line_num = self.GetCurrentLine() if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown(): # Capture Control-C if self._input_state == 'subprocess': @@ -413,7 +434,7 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): else: # Up history if event.KeyCode == wx.WXK_UP and ( - ( current_line_number == self.current_prompt_line and + ( current_line_num == self.current_prompt_line and event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) ) or event.ControlDown() ): new_buffer = self.get_history_previous( @@ -425,7 +446,7 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): self.GotoPos(self.current_prompt_pos) # Down history elif event.KeyCode == wx.WXK_DOWN and ( - ( current_line_number == self.LineCount -1 and + ( current_line_num == self.LineCount -1 and event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) ) or event.ControlDown() ): new_buffer = self.get_history_next() @@ -433,15 +454,43 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): self.input_buffer = new_buffer # Tab-completion elif event.KeyCode == ord('\t'): - current_line, current_line_number = self.CurLine + current_line, current_line_num = self.CurLine if not re.match(r'^\s*$', current_line): self.complete_current_input() if self.AutoCompActive(): wx.CallAfter(self._popup_completion, create=True) else: event.Skip() + elif event.KeyCode == wx.WXK_BACK: + # If characters where erased, check if we have to + # remove a line. + # XXX: What about DEL? + # FIXME: This logics should be in ConsoleWidget, as it is + # independant of IPython + current_line, _ = self.CurLine + current_pos = self.GetCurrentPos() + current_line_num = self.LineFromPosition(current_pos) + current_col = self.GetColumn(current_pos) + len_prompt = len(self.continuation_prompt()) + if ( current_line.startswith(self.continuation_prompt()) + and current_col == len_prompt): + new_lines = [] + for line_num, line in enumerate( + self.input_buffer.split('\n')): + if (line_num + self.current_prompt_line == + current_line_num): + new_lines.append(line[len_prompt:]) + else: + new_lines.append('\n'+line) + # The first character is '\n', due to the above + # code: + self.input_buffer = ''.join(new_lines)[1:] + self.GotoPos(current_pos - 1 - len_prompt) + else: + ConsoleWidget._on_key_down(self, event, skip=skip) else: ConsoleWidget._on_key_down(self, event, skip=skip) + def _on_key_up(self, event, skip=True): @@ -453,14 +502,40 @@ class WxController(ConsoleWidget, PrefilterFrontEnd): wx.CallAfter(self._popup_completion, create=True) else: ConsoleWidget._on_key_up(self, event, skip=skip) + # Make sure the continuation_prompts are always followed by a + # whitespace + new_lines = [] + if self._input_state == 'readline': + position = self.GetCurrentPos() + continuation_prompt = self.continuation_prompt()[:-1] + for line in self.input_buffer.split('\n'): + if not line == continuation_prompt: + new_lines.append(line) + self.input_buffer = '\n'.join(new_lines) + self.GotoPos(position) def _on_enter(self): """ Called on return key down, in readline input_state. """ + last_line_num = self.LineFromPosition(self.GetLength()) + current_line_num = self.LineFromPosition(self.GetCurrentPos()) + new_line_pos = (last_line_num - current_line_num) if self.debug: print >>sys.__stdout__, repr(self.input_buffer) - PrefilterFrontEnd._on_enter(self) + self.write('\n', refresh=False) + # Under windows scintilla seems to be doing funny + # stuff to the line returns here, but the getter for + # input_buffer filters this out. + if sys.platform == 'win32': + self.input_buffer = self.input_buffer + old_prompt_num = self.current_prompt_pos + has_executed = PrefilterFrontEnd._on_enter(self, + new_line_pos=new_line_pos) + if old_prompt_num == self.current_prompt_pos: + # No execution has happened + self.GotoPos(self.GetLineEndPosition(current_line_num + 1)) + return has_executed #-------------------------------------------------------------------------- diff --git a/IPython/genutils.py b/IPython/genutils.py index 85f8d2d..d4fc9b9 100644 --- a/IPython/genutils.py +++ b/IPython/genutils.py @@ -1007,7 +1007,17 @@ def get_security_dir(): else: os.chmod(security_dir, 0700) return security_dir - + +def get_log_dir(): + """Get the IPython log directory. + + If the log directory does not exist, it is created. + """ + log_dir = os.path.join(get_ipython_dir(), 'log') + if not os.path.isdir(log_dir): + os.mkdir(log_dir, 0777) + return log_dir + #**************************************************************************** # strings and text diff --git a/IPython/iplib.py b/IPython/iplib.py index d674668..c6515ef 100644 --- a/IPython/iplib.py +++ b/IPython/iplib.py @@ -152,7 +152,11 @@ def user_setup(ipythondir,rc_suffix,mode='install',interactive=True): printf = lambda s : None # Install mode should be re-entrant: if the install dir already exists, - # bail out cleanly + # bail out cleanly. + # XXX. This is too hasty to return. We need to check to make sure that + # all the expected config files and directories are actually there. We + # currently have a failure mode if someone deletes a needed config file + # but still has the ipythondir. if mode == 'install' and os.path.isdir(ipythondir): return @@ -1474,8 +1478,9 @@ class InteractiveShell(object,Magic): #print "loading rl:",rlcommand # dbg readline.parse_and_bind(rlcommand) - # remove some chars from the delimiters list - delims = readline.get_completer_delims() + # Remove some chars from the delimiters list. If we encounter + # unicode chars, discard them. + delims = readline.get_completer_delims().encode("ascii", "ignore") delims = delims.translate(string._idmap, self.rc.readline_remove_delims) readline.set_completer_delims(delims) diff --git a/IPython/ipmaker.py b/IPython/ipmaker.py index 08244d4..f514338 100644 --- a/IPython/ipmaker.py +++ b/IPython/ipmaker.py @@ -55,9 +55,9 @@ from IPython.iplib import InteractiveShell from IPython.usage import cmd_line_usage,interactive_usage from IPython.genutils import * -def force_import(modname): - if modname in sys.modules: - print "reload",modname +def force_import(modname,force_reload=False): + if modname in sys.modules and force_reload: + info("reloading: %s" % modname) reload(sys.modules[modname]) else: __import__(modname) @@ -625,25 +625,25 @@ object? -> Details about 'object'. ?object also works, ?? prints more. except: IP.InteractiveTB() import_fail_info('ipy_system_conf') - + # only import prof module if ipythonrc-PROF was not found if opts_all.profile and not profile_handled_by_legacy: profmodname = 'ipy_profile_' + opts_all.profile try: - force_import(profmodname) except: IP.InteractiveTB() - print "Error importing",profmodname,"- perhaps you should run %upgrade?" + print "Error importing",profmodname,\ + "- perhaps you should run %upgrade?" import_fail_info(profmodname) else: opts.profile = opts_all.profile else: force_import('ipy_profile_none') + # XXX - this is wrong: ipy_user_conf should not be loaded unconditionally, + # since the user could have specified a config file path by hand. try: - force_import('ipy_user_conf') - except: conf = opts_all.ipythondir + "/ipy_user_conf.py" IP.InteractiveTB() diff --git a/IPython/kernel/__init__.py b/IPython/kernel/__init__.py index ce2ad73..cd8d856 100755 --- a/IPython/kernel/__init__.py +++ b/IPython/kernel/__init__.py @@ -15,10 +15,11 @@ if they need blocking clients or in `asyncclient.py` if they want asynchronous, deferred/Twisted using clients. """ __docformat__ = "restructuredtext en" -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- # Copyright (C) 2008 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. -#------------------------------------------------------------------------------- - \ No newline at end of file +#----------------------------------------------------------------------------- + +from IPython.kernel.error import TaskRejectError \ No newline at end of file diff --git a/IPython/kernel/config/__init__.py b/IPython/kernel/config/__init__.py index 7a200bb..b4845dc 100644 --- a/IPython/kernel/config/__init__.py +++ b/IPython/kernel/config/__init__.py @@ -15,6 +15,7 @@ __docformat__ = "restructuredtext en" # Imports #------------------------------------------------------------------------------- +import os, sys from os.path import join as pjoin from IPython.external.configobj import ConfigObj @@ -23,6 +24,7 @@ from IPython.genutils import get_ipython_dir, get_security_dir default_kernel_config = ConfigObj() +# This will raise OSError if ipythondir doesn't exist. security_dir = get_security_dir() #------------------------------------------------------------------------------- diff --git a/IPython/kernel/core/interpreter.py b/IPython/kernel/core/interpreter.py index bca170b..05cec24 100644 --- a/IPython/kernel/core/interpreter.py +++ b/IPython/kernel/core/interpreter.py @@ -679,21 +679,22 @@ class Interpreter(object): # to exec will fail however. There seems to be some inconsistency in # how trailing whitespace is handled, but this seems to work. python = python.strip() - + # The compiler module does not like unicode. We need to convert # it encode it: if isinstance(python, unicode): # Use the utf-8-sig BOM so the compiler detects this a UTF-8 # encode string. python = '\xef\xbb\xbf' + python.encode('utf-8') - + # The compiler module will parse the code into an abstract syntax tree. + # This has a bug with str("a\nb"), but not str("""a\nb""")!!! ast = compiler.parse(python) - + # Uncomment to help debug the ast tree # for n in ast.node: # print n.lineno,'->',n - + # Each separate command is available by iterating over ast.node. The # lineno attribute is the line number (1-indexed) beginning the commands # suite. @@ -703,20 +704,26 @@ class Interpreter(object): # We might eventually discover other cases where lineno is None and have # to put in a more sophisticated test. linenos = [x.lineno-1 for x in ast.node if x.lineno is not None] - + # When we finally get the slices, we will need to slice all the way to # the end even though we don't have a line number for it. Fortunately, # None does the job nicely. linenos.append(None) + + # Same problem at the other end: sometimes the ast tree has its + # first complete statement not starting on line 0. In this case + # we might miss part of it. This fixes ticket 266993. Thanks Gael! + linenos[0] = 0 + lines = python.splitlines() - + # Create a list of atomic commands. cmds = [] for i, j in zip(linenos[:-1], linenos[1:]): cmd = lines[i:j] if cmd: cmds.append('\n'.join(cmd)+'\n') - + return cmds def error(self, text): diff --git a/IPython/kernel/core/notification.py b/IPython/kernel/core/notification.py index af5d236..7762d3a 100644 --- a/IPython/kernel/core/notification.py +++ b/IPython/kernel/core/notification.py @@ -15,6 +15,8 @@ __docformat__ = "restructuredtext en" # the file COPYING, distributed as part of this software. #----------------------------------------------------------------------------- +# Tell nose to skip the testing of this module +__test__ = {} class NotificationCenter(object): """Synchronous notification center diff --git a/IPython/kernel/core/tests/test_interpreter.py b/IPython/kernel/core/tests/test_interpreter.py index 3efc4f2..bee4d9a 100644 --- a/IPython/kernel/core/tests/test_interpreter.py +++ b/IPython/kernel/core/tests/test_interpreter.py @@ -2,25 +2,61 @@ """This file contains unittests for the interpreter.py module.""" -__docformat__ = "restructuredtext en" - #----------------------------------------------------------------------------- -# Copyright (C) 2008 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. +# Copyright (C) 2008-2009 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 +# Imports #----------------------------------------------------------------------------- +# Tell nose to skip this module +__test__ = {} + +from twisted.trial import unittest from IPython.kernel.core.interpreter import Interpreter -def test_unicode(): - """ Test unicode handling with the interpreter. - """ - i = Interpreter() - i.execute_python(u'print "ù"') - i.execute_python('print "ù"') +#----------------------------------------------------------------------------- +# Tests +#----------------------------------------------------------------------------- + +class TestInterpreter(unittest.TestCase): + + def test_unicode(self): + """ Test unicode handling with the interpreter.""" + i = Interpreter() + i.execute_python(u'print "ù"') + i.execute_python('print "ù"') + + def test_ticket266993(self): + """ Test for ticket 266993.""" + i = Interpreter() + i.execute('str("""a\nb""")') + + def test_ticket364347(self): + """Test for ticket 364347.""" + i = Interpreter() + i.split_commands('str("a\\nb")') + + def test_split_commands(self): + """ Test that commands are indeed individually split.""" + i = Interpreter() + test_atoms = [('(1\n + 1)', ), + ('1', '1', ), + ] + for atoms in test_atoms: + atoms = [atom.rstrip() + '\n' for atom in atoms] + self.assertEquals(i.split_commands(''.join(atoms)),atoms) + + def test_long_lines(self): + """ Test for spurious syntax error created by the interpreter.""" + test_strings = [u'( 1 +\n 1\n )\n\n', + u'(1 \n + 1\n )\n\n', + ] + i = Interpreter() + for s in test_strings: + i.execute(s) diff --git a/IPython/kernel/core/tests/test_notification.py b/IPython/kernel/core/tests/test_notification.py index 07d9286..2744049 100644 --- a/IPython/kernel/core/tests/test_notification.py +++ b/IPython/kernel/core/tests/test_notification.py @@ -2,26 +2,26 @@ """This file contains unittests for the notification.py module.""" -__docformat__ = "restructuredtext en" - #----------------------------------------------------------------------------- -# Copyright (C) 2008 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. +# Copyright (C) 2008-2009 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 +# Imports #----------------------------------------------------------------------------- -import unittest +# Tell nose to skip this module +__test__ = {} + +from twisted.trial import unittest import IPython.kernel.core.notification as notification -from nose.tools import timed -# -# Supporting test classes -# +#----------------------------------------------------------------------------- +# Support Classes +#----------------------------------------------------------------------------- class Observer(object): """docstring for Observer""" @@ -36,7 +36,6 @@ class Observer(object): self.expectedType, self.expectedSender) - def callback(self, theType, sender, args={}): """callback""" @@ -47,7 +46,6 @@ class Observer(object): assert(args == self.expectedKwArgs) self.recieved = True - def verify(self): """verify""" @@ -57,7 +55,6 @@ class Observer(object): """reset""" self.recieved = False - class Notifier(object): @@ -72,11 +69,10 @@ class Notifier(object): center.post_notification(self.theType, self, **self.kwargs) - -# -# Test Cases -# +#----------------------------------------------------------------------------- +# Tests +#----------------------------------------------------------------------------- class NotificationTests(unittest.TestCase): """docstring for NotificationTests""" @@ -94,7 +90,6 @@ class NotificationTests(unittest.TestCase): observer.verify() - def test_type_specificity(self): """Test that observers are registered by type""" @@ -109,7 +104,6 @@ class NotificationTests(unittest.TestCase): observer.verify() - def test_sender_specificity(self): """Test that observers are registered by sender""" @@ -123,7 +117,6 @@ class NotificationTests(unittest.TestCase): observer.verify() - def test_remove_all_observers(self): """White-box test for remove_all_observers""" @@ -136,8 +129,7 @@ class NotificationTests(unittest.TestCase): notification.sharedCenter.remove_all_observers() self.assert_(len(notification.sharedCenter.observers) == 0, "observers removed") - - + def test_any_sender(self): """test_any_sender""" @@ -153,9 +145,7 @@ class NotificationTests(unittest.TestCase): observer.reset() sender2.post() observer.verify() - - - @timed(.01) + def test_post_performance(self): """Test that post_notification, even with many registered irrelevant observers is fast""" @@ -168,4 +158,4 @@ class NotificationTests(unittest.TestCase): notification.sharedCenter.post_notification('EXPECTED_TYPE', self) o.verify() - + diff --git a/IPython/kernel/core/tests/test_redirectors.py b/IPython/kernel/core/tests/test_redirectors.py index 81aa0ba..3b0e416 100644 --- a/IPython/kernel/core/tests/test_redirectors.py +++ b/IPython/kernel/core/tests/test_redirectors.py @@ -2,69 +2,77 @@ """ Test the output capture at the OS level, using file descriptors. """ - -__docformat__ = "restructuredtext en" - -#------------------------------------------------------------------------------- -# Copyright (C) 2008 The IPython Development Team +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 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 +#----------------------------------------------------------------------------- + +# Tell nose to skip this module +__test__ = {} -# Stdlib imports -import os from cStringIO import StringIO +import os + +from twisted.trial import unittest -# Our own imports -from IPython.testing import decorators as dec +from IPython.testing import decorators_trial as dec #----------------------------------------------------------------------------- -# Test functions +# Tests +#----------------------------------------------------------------------------- -@dec.skip_win32 -def test_redirector(): - """ Checks that the redirector can be used to do synchronous capture. - """ - from IPython.kernel.core.fd_redirector import FDRedirector - r = FDRedirector() - out = StringIO() - try: - r.start() - for i in range(10): - os.system('echo %ic' % i) - print >>out, r.getvalue(), - print >>out, i - except: - r.stop() - raise - r.stop() - result1 = out.getvalue() - result2 = "".join("%ic\n%i\n" %(i, i) for i in range(10)) - assert result1 == result2 +class TestRedirector(unittest.TestCase): -@dec.skip_win32 -def test_redirector_output_trap(): - """ This test check not only that the redirector_output_trap does + @dec.skip_win32 + def test_redirector(self): + """Checks that the redirector can be used to do synchronous capture. + """ + from IPython.kernel.core.fd_redirector import FDRedirector + r = FDRedirector() + out = StringIO() + try: + r.start() + for i in range(10): + os.system('echo %ic' % i) + print >>out, r.getvalue(), + print >>out, i + except: + r.stop() + raise + r.stop() + result1 = out.getvalue() + result2 = "".join("%ic\n%i\n" %(i, i) for i in range(10)) + self.assertEquals(result1, result2) + + @dec.skip_win32 + def test_redirector_output_trap(self): + """Check the greedy trapping behavior of the traps. + + This test check not only that the redirector_output_trap does trap the output, but also that it does it in a gready way, that is by calling the callback ASAP. - """ - from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap - out = StringIO() - trap = RedirectorOutputTrap(out.write, out.write) - try: - trap.set() - for i in range(10): - os.system('echo %ic' % i) - print "%ip" % i - print >>out, i - except: + """ + from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap + out = StringIO() + trap = RedirectorOutputTrap(out.write, out.write) + try: + trap.set() + for i in range(10): + os.system('echo %ic' % i) + print "%ip" % i + print >>out, i + except: + trap.unset() + raise trap.unset() - raise - trap.unset() - result1 = out.getvalue() - result2 = "".join("%ic\n%ip\n%i\n" %(i, i, i) for i in range(10)) - assert result1 == result2 - + result1 = out.getvalue() + result2 = "".join("%ic\n%ip\n%i\n" %(i, i, i) for i in range(10)) + self.assertEquals(result1, result2) + diff --git a/IPython/kernel/core/ultraTB.py b/IPython/kernel/core/ultraTB.py index a11a0eb..c76aa25 100644 --- a/IPython/kernel/core/ultraTB.py +++ b/IPython/kernel/core/ultraTB.py @@ -268,6 +268,8 @@ def _formatTracebackLines(lnum, index, lines, Colors, lvals=None,scheme=None): # This lets us get fully syntax-highlighted tracebacks. if scheme is None: try: + # Again, reference to a global __IPYTHON__ that doesn't exist. + # XXX scheme = __IPYTHON__.rc.colors except: scheme = DEFAULT_SCHEME @@ -487,10 +489,14 @@ class ListTB(TBTools): else: list.append('%s\n' % str(stype)) - # vds:>> - if have_filedata: - __IPYTHON__.hooks.synchronize_with_editor(filename, lineno, 0) - # vds:<< + # This is being commented out for now as the __IPYTHON__ variable + # referenced here is not resolved and causes massive test failures + # and errors. B. Granger, 04/2009. XXX + # See https://bugs.launchpad.net/bugs/362137 + # # vds:>> + # if have_filedata: + # __IPYTHON__.hooks.synchronize_with_editor(filename, lineno, 0) + # # vds:<< return list @@ -804,13 +810,17 @@ class VerboseTB(TBTools): value = text_repr(getattr(evalue, name)) exception.append('\n%s%s = %s' % (indent, name, value)) - # vds: >> - if records: - filepath, lnum = records[-1][1:3] - #print "file:", str(file), "linenb", str(lnum) # dbg - filepath = os.path.abspath(filepath) - __IPYTHON__.hooks.synchronize_with_editor(filepath, lnum, 0) - # vds: << + # This is being commented out for now as the __IPYTHON__ variable + # referenced here is not resolved and causes massive test failures + # and errors. B. Granger, 04/2009. XXX + # See https://bugs.launchpad.net/bugs/362137 + # # vds: >> + # if records: + # filepath, lnum = records[-1][1:3] + # #print "file:", str(file), "linenb", str(lnum) # dbg + # filepath = os.path.abspath(filepath) + # __IPYTHON__.hooks.synchronize_with_editor(filepath, lnum, 0) + # # vds: << # return all our info assembled as a single string return '%s\n\n%s\n%s' % (head,'\n'.join(frames),''.join(exception[0]) ) diff --git a/IPython/kernel/engineconnector.py b/IPython/kernel/engineconnector.py index c7be8a9..389a26d 100644 --- a/IPython/kernel/engineconnector.py +++ b/IPython/kernel/engineconnector.py @@ -67,10 +67,10 @@ class EngineConnector(object): self.furl = find_furl(furl_or_file) except ValueError: return defer.fail(failure.Failure()) - # return defer.fail(failure.Failure(ValueError('not a valid furl or furl file: %r' % furl_or_file))) - d = self.tub.getReference(self.furl) - d.addCallbacks(self._register, self._log_failure) - return d + else: + d = self.tub.getReference(self.furl) + d.addCallbacks(self._register, self._log_failure) + return d def _log_failure(self, reason): log.err('EngineConnector: engine registration failed:') diff --git a/IPython/kernel/engineservice.py b/IPython/kernel/engineservice.py index b301f0f..b400320 100644 --- a/IPython/kernel/engineservice.py +++ b/IPython/kernel/engineservice.py @@ -34,6 +34,9 @@ __docformat__ = "restructuredtext en" # Imports #------------------------------------------------------------------------------- +# Tell nose to skip the testing of this module +__test__ = {} + import os, sys, copy import cPickle as pickle from new import instancemethod @@ -266,8 +269,8 @@ class StrictDict(dict): pickle.dumps(key, 2) pickle.dumps(value, 2) newvalue = copy.deepcopy(value) - except: - raise error.InvalidProperty(value) + except Exception, e: + raise error.InvalidProperty("can't be a value: %r" % value) dict.__setitem__(self, key, newvalue) self.modified = True diff --git a/IPython/kernel/error.py b/IPython/kernel/error.py index 3aaa78c..d91f9e0 100644 --- a/IPython/kernel/error.py +++ b/IPython/kernel/error.py @@ -104,6 +104,23 @@ class StopLocalExecution(KernelError): class SecurityError(KernelError): pass +class FileTimeoutError(KernelError): + pass + +class TaskRejectError(KernelError): + """Exception to raise when a task should be rejected by an engine. + + This exception can be used to allow a task running on an engine to test + if the engine (or the user's namespace on the engine) has the needed + task dependencies. If not, the task should raise this exception. For + the task to be retried on another engine, the task should be created + with the `retries` argument > 1. + + The advantage of this approach over our older properties system is that + tasks have full access to the user's namespace on the engines and the + properties don't have to be managed or tested by the controller. + """ + class CompositeError(KernelError): def __init__(self, message, elist): Exception.__init__(self, *(message, elist)) diff --git a/IPython/kernel/multiengineclient.py b/IPython/kernel/multiengineclient.py index 2f7ad16..4281df8 100644 --- a/IPython/kernel/multiengineclient.py +++ b/IPython/kernel/multiengineclient.py @@ -20,6 +20,7 @@ import sys import cPickle as pickle from types import FunctionType import linecache +import warnings from twisted.internet import reactor from twisted.python import components, log @@ -389,6 +390,14 @@ def strip_whitespace(source): #------------------------------------------------------------------------------- +_prop_warn = """\ + +We are currently refactoring the task dependency system. This might +involve the removal of this method and other methods related to engine +properties. Please see the docstrings for IPython.kernel.TaskRejectError +for more information.""" + + class IFullBlockingMultiEngineClient(Interface): pass @@ -730,22 +739,27 @@ class FullBlockingMultiEngineClient(InteractiveMultiEngineClient): return self._blockFromThread(self.smultiengine.queue_status, targets=targets, block=block) def set_properties(self, properties, targets=None, block=None): + warnings.warn(_prop_warn) targets, block = self._findTargetsAndBlock(targets, block) return self._blockFromThread(self.smultiengine.set_properties, properties, targets=targets, block=block) def get_properties(self, keys=None, targets=None, block=None): + warnings.warn(_prop_warn) targets, block = self._findTargetsAndBlock(targets, block) return self._blockFromThread(self.smultiengine.get_properties, keys, targets=targets, block=block) def has_properties(self, keys, targets=None, block=None): + warnings.warn(_prop_warn) targets, block = self._findTargetsAndBlock(targets, block) return self._blockFromThread(self.smultiengine.has_properties, keys, targets=targets, block=block) def del_properties(self, keys, targets=None, block=None): + warnings.warn(_prop_warn) targets, block = self._findTargetsAndBlock(targets, block) return self._blockFromThread(self.smultiengine.del_properties, keys, targets=targets, block=block) def clear_properties(self, targets=None, block=None): + warnings.warn(_prop_warn) targets, block = self._findTargetsAndBlock(targets, block) return self._blockFromThread(self.smultiengine.clear_properties, targets=targets, block=block) diff --git a/IPython/kernel/scripts/ipcluster.py b/IPython/kernel/scripts/ipcluster.py index ca20901..34b2d61 100755 --- a/IPython/kernel/scripts/ipcluster.py +++ b/IPython/kernel/scripts/ipcluster.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python + #!/usr/bin/env python # encoding: utf-8 """Start an IPython cluster = (controller + engines).""" @@ -29,29 +29,35 @@ from twisted.python import failure, log from IPython.external import argparse from IPython.external import Itpl -from IPython.genutils import get_ipython_dir, num_cpus +from IPython.genutils import ( + get_ipython_dir, + get_log_dir, + get_security_dir, + num_cpus +) from IPython.kernel.fcutil import have_crypto -from IPython.kernel.error import SecurityError + +# Create various ipython directories if they don't exist. +# This must be done before IPython.kernel.config is imported. +from IPython.iplib import user_setup +if os.name == 'posix': + rc_suffix = '' +else: + rc_suffix = '.ini' +user_setup(get_ipython_dir(), rc_suffix, mode='install', interactive=False) +get_log_dir() +get_security_dir() + +from IPython.kernel.config import config_manager as kernel_config_manager +from IPython.kernel.error import SecurityError, FileTimeoutError from IPython.kernel.fcutil import have_crypto -from IPython.kernel.twistedutil import gatherBoth +from IPython.kernel.twistedutil import gatherBoth, wait_for_file from IPython.kernel.util import printer - #----------------------------------------------------------------------------- # General process handling code #----------------------------------------------------------------------------- -def find_exe(cmd): - try: - import win32api - except ImportError: - raise ImportError('you need to have pywin32 installed for this to work') - else: - try: - (path, offest) = win32api.SearchPath(os.environ['PATH'],cmd + '.exe') - except: - (path, offset) = win32api.SearchPath(os.environ['PATH'],cmd + '.bat') - return path class ProcessStateError(Exception): pass @@ -184,8 +190,10 @@ class ControllerLauncher(ProcessLauncher): from IPython.kernel.scripts import ipcontroller script_location = ipcontroller.__file__.replace('.pyc', '.py') # The -u option here turns on unbuffered output, which is required - # on Win32 to prevent wierd conflict and problems with Twisted - args = [find_exe('python'), '-u', script_location] + # on Win32 to prevent wierd conflict and problems with Twisted. + # Also, use sys.executable to make sure we are picking up the + # right python exe. + args = [sys.executable, '-u', script_location] else: args = ['ipcontroller'] self.extra_args = extra_args @@ -204,8 +212,10 @@ class EngineLauncher(ProcessLauncher): from IPython.kernel.scripts import ipengine script_location = ipengine.__file__.replace('.pyc', '.py') # The -u option here turns on unbuffered output, which is required - # on Win32 to prevent wierd conflict and problems with Twisted - args = [find_exe('python'), '-u', script_location] + # on Win32 to prevent wierd conflict and problems with Twisted. + # Also, use sys.executable to make sure we are picking up the + # right python exe. + args = [sys.executable, '-u', script_location] else: args = ['ipengine'] self.extra_args = extra_args @@ -465,7 +475,9 @@ class SSHEngineSet(object): # The main functions should then just parse the command line arguments, create # the appropriate class and call a 'start' method. + def check_security(args, cont_args): + """Check to see if we should run with SSL support.""" if (not args.x or not args.y) and not have_crypto: log.err(""" OpenSSL/pyOpenSSL is not available, so we can't run in secure mode. @@ -478,7 +490,9 @@ Try running ipcluster with the -xy flags: ipcluster local -xy -n 4""") cont_args.append('-y') return True + def check_reuse(args, cont_args): + """Check to see if we should try to resuse FURL files.""" if args.r: cont_args.append('-r') if args.client_port == 0 or args.engine_port == 0: @@ -491,6 +505,25 @@ the --client-port and --engine-port options.""") cont_args.append('--engine-port=%i' % args.engine_port) return True + +def _err_and_stop(f): + """Errback to log a failure and halt the reactor on a fatal error.""" + log.err(f) + reactor.stop() + + +def _delay_start(cont_pid, start_engines, furl_file, reuse): + """Wait for controller to create FURL files and the start the engines.""" + if not reuse: + if os.path.isfile(furl_file): + os.unlink(furl_file) + log.msg('Waiting for controller to finish starting...') + d = wait_for_file(furl_file, delay=0.2, max_tries=50) + d.addCallback(lambda _: log.msg('Controller started')) + d.addCallback(lambda _: start_engines(cont_pid)) + return d + + def main_local(args): cont_args = [] cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller')) @@ -520,13 +553,10 @@ def main_local(args): signal.signal(signal.SIGINT,shutdown) d = eset.start(args.n) return d - def delay_start(cont_pid): - # This is needed because the controller doesn't start listening - # right when it starts and the controller needs to write - # furl files for the engine to pick up - reactor.callLater(1.0, start_engines, cont_pid) - dstart.addCallback(delay_start) - dstart.addErrback(lambda f: f.raiseException()) + config = kernel_config_manager.get_config_obj() + furl_file = config['controller']['engine_furl_file'] + dstart.addCallback(_delay_start, start_engines, furl_file, args.r) + dstart.addErrback(_err_and_stop) def main_mpi(args): @@ -562,13 +592,10 @@ def main_mpi(args): signal.signal(signal.SIGINT,shutdown) d = eset.start() return d - def delay_start(cont_pid): - # This is needed because the controller doesn't start listening - # right when it starts and the controller needs to write - # furl files for the engine to pick up - reactor.callLater(1.0, start_engines, cont_pid) - dstart.addCallback(delay_start) - dstart.addErrback(lambda f: f.raiseException()) + config = kernel_config_manager.get_config_obj() + furl_file = config['controller']['engine_furl_file'] + dstart.addCallback(_delay_start, start_engines, furl_file, args.r) + dstart.addErrback(_err_and_stop) def main_pbs(args): @@ -595,8 +622,10 @@ def main_pbs(args): signal.signal(signal.SIGINT,shutdown) d = pbs_set.start(args.n) return d - dstart.addCallback(start_engines) - dstart.addErrback(lambda f: f.raiseException()) + config = kernel_config_manager.get_config_obj() + furl_file = config['controller']['engine_furl_file'] + dstart.addCallback(_delay_start, start_engines, furl_file, args.r) + dstart.addErrback(_err_and_stop) def main_ssh(args): @@ -637,12 +666,10 @@ def main_ssh(args): signal.signal(signal.SIGINT,shutdown) d = ssh_set.start(clusterfile['send_furl']) return d - - def delay_start(cont_pid): - reactor.callLater(1.0, start_engines, cont_pid) - - dstart.addCallback(delay_start) - dstart.addErrback(lambda f: f.raiseException()) + config = kernel_config_manager.get_config_obj() + furl_file = config['controller']['engine_furl_file'] + dstart.addCallback(_delay_start, start_engines, furl_file, args.r) + dstart.addErrback(_err_and_stop) def get_args(): @@ -697,8 +724,11 @@ def get_args(): parser = argparse.ArgumentParser( description='IPython cluster startup. This starts a controller and\ - engines using various approaches. THIS IS A TECHNOLOGY PREVIEW AND\ - THE API WILL CHANGE SIGNIFICANTLY BEFORE THE FINAL RELEASE.' + engines using various approaches. Use the IPYTHONDIR environment\ + variable to change your IPython directory from the default of\ + .ipython or _ipython. The log and security subdirectories of your\ + IPython directory will be used by this script for log files and\ + security files.' ) subparsers = parser.add_subparsers( help='available cluster types. For help, do "ipcluster TYPE --help"') diff --git a/IPython/kernel/scripts/ipcontroller.py b/IPython/kernel/scripts/ipcontroller.py index 2606577..496b139 100755 --- a/IPython/kernel/scripts/ipcontroller.py +++ b/IPython/kernel/scripts/ipcontroller.py @@ -21,8 +21,10 @@ __docformat__ = "restructuredtext en" import sys sys.path.insert(0, '') -import sys, time, os from optparse import OptionParser +import os +import time +import tempfile from twisted.application import internet, service from twisted.internet import reactor, error, defer @@ -37,6 +39,18 @@ from IPython.kernel.error import SecurityError from IPython.kernel import controllerservice from IPython.kernel.fcutil import check_furl_file_security +# Create various ipython directories if they don't exist. +# This must be done before IPython.kernel.config is imported. +from IPython.iplib import user_setup +from IPython.genutils import get_ipython_dir, get_log_dir, get_security_dir +if os.name == 'posix': + rc_suffix = '' +else: + rc_suffix = '.ini' +user_setup(get_ipython_dir(), rc_suffix, mode='install', interactive=False) +get_log_dir() +get_security_dir() + from IPython.kernel.config import config_manager as kernel_config_manager from IPython.config.cutils import import_item @@ -45,6 +59,10 @@ from IPython.config.cutils import import_item # Code #------------------------------------------------------------------------------- +def get_temp_furlfile(filename): + return tempfile.mktemp(dir=os.path.dirname(filename), + prefix=os.path.basename(filename)) + def make_tub(ip, port, secure, cert_file): """ Create a listening tub given an ip, port, and cert_file location. @@ -107,13 +125,18 @@ def make_client_service(controller_service, config): """Set the location for the tub and return a deferred.""" def register(empty, ref, furl_file): - client_tub.registerReference(ref, furlFile=furl_file) + # We create and then move to make sure that when the file + # appears to other processes, the buffer has the flushed + # and the file has been closed + temp_furl_file = get_temp_furlfile(furl_file) + client_tub.registerReference(ref, furlFile=temp_furl_file) + os.rename(temp_furl_file, furl_file) if location == '': d = client_tub.setLocationAutomatically() else: d = defer.maybeDeferred(client_tub.setLocation, "%s:%i" % (location, client_listener.getPortnum())) - + for ciname, ci in config['controller']['controller_interfaces'].iteritems(): log.msg("Adapting Controller to interface: %s" % ciname) furl_file = ci['furl_file'] @@ -154,7 +177,12 @@ def make_engine_service(controller_service, config): """Set the location for the tub and return a deferred.""" def register(empty, ref, furl_file): - engine_tub.registerReference(ref, furlFile=furl_file) + # We create and then move to make sure that when the file + # appears to other processes, the buffer has the flushed + # and the file has been closed + temp_furl_file = get_temp_furlfile(furl_file) + engine_tub.registerReference(ref, furlFile=temp_furl_file) + os.rename(temp_furl_file, furl_file) if location == '': d = engine_tub.setLocationAutomatically() @@ -236,7 +264,14 @@ def init_config(): Initialize the configuration using default and command line options. """ - parser = OptionParser() + parser = OptionParser("""ipcontroller [options] + +Start an IPython controller. + +Use the IPYTHONDIR environment variable to change your IPython directory +from the default of .ipython or _ipython. The log and security +subdirectories of your IPython directory will be used by this script +for log files and security files.""") # Client related options parser.add_option( @@ -325,12 +360,6 @@ def init_config(): help="log file name (default is stdout)" ) parser.add_option( - "--ipythondir", - type="string", - dest="ipythondir", - help="look for config files and profiles in this directory" - ) - parser.add_option( "-r", action="store_true", dest="reuse_furls", @@ -339,7 +368,6 @@ def init_config(): (options, args) = parser.parse_args() - kernel_config_manager.update_config_obj_from_default_file(options.ipythondir) config = kernel_config_manager.get_config_obj() # Update with command line options diff --git a/IPython/kernel/scripts/ipengine.py b/IPython/kernel/scripts/ipengine.py index cd671f1..a70ec6a 100755 --- a/IPython/kernel/scripts/ipengine.py +++ b/IPython/kernel/scripts/ipengine.py @@ -21,8 +21,8 @@ __docformat__ = "restructuredtext en" import sys sys.path.insert(0, '') -import sys, os from optparse import OptionParser +import os from twisted.application import service from twisted.internet import reactor @@ -33,6 +33,19 @@ from IPython.kernel.fcutil import Tub, UnauthenticatedTub from IPython.kernel.core.config import config_manager as core_config_manager from IPython.config.cutils import import_item from IPython.kernel.engineservice import EngineService + +# Create various ipython directories if they don't exist. +# This must be done before IPython.kernel.config is imported. +from IPython.iplib import user_setup +from IPython.genutils import get_ipython_dir, get_log_dir, get_security_dir +if os.name == 'posix': + rc_suffix = '' +else: + rc_suffix = '.ini' +user_setup(get_ipython_dir(), rc_suffix, mode='install', interactive=False) +get_log_dir() +get_security_dir() + from IPython.kernel.config import config_manager as kernel_config_manager from IPython.kernel.engineconnector import EngineConnector @@ -106,13 +119,19 @@ def start_engine(): engine_connector = EngineConnector(tub_service) furl_file = kernel_config['engine']['furl_file'] log.msg("Using furl file: %s" % furl_file) - d = engine_connector.connect_to_controller(engine_service, furl_file) - def handle_error(f): - log.err(f) - if reactor.running: - reactor.stop() - d.addErrback(handle_error) + def call_connect(engine_service, furl_file): + d = engine_connector.connect_to_controller(engine_service, furl_file) + def handle_error(f): + # If this print statement is replaced by a log.err(f) I get + # an unhandled error, which makes no sense. I shouldn't have + # to use a print statement here. My only thought is that + # at the beginning of the process the logging is still starting up + print "error connecting to controller:", f.getErrorMessage() + reactor.callLater(0.1, reactor.stop) + d.addErrback(handle_error) + + reactor.callWhenRunning(call_connect, engine_service, furl_file) reactor.run() @@ -121,7 +140,14 @@ def init_config(): Initialize the configuration using default and command line options. """ - parser = OptionParser() + parser = OptionParser("""ipengine [options] + +Start an IPython engine. + +Use the IPYTHONDIR environment variable to change your IPython directory +from the default of .ipython or _ipython. The log and security +subdirectories of your IPython directory will be used by this script +for log files and security files.""") parser.add_option( "--furl-file", @@ -142,18 +168,9 @@ def init_config(): dest="logfile", help="log file name (default is stdout)" ) - parser.add_option( - "--ipythondir", - type="string", - dest="ipythondir", - help="look for config files and profiles in this directory" - ) (options, args) = parser.parse_args() - kernel_config_manager.update_config_obj_from_default_file(options.ipythondir) - core_config_manager.update_config_obj_from_default_file(options.ipythondir) - kernel_config = kernel_config_manager.get_config_obj() # Now override with command line options if options.furl_file is not None: diff --git a/IPython/kernel/task.py b/IPython/kernel/task.py index 5841a0f..79a691b 100644 --- a/IPython/kernel/task.py +++ b/IPython/kernel/task.py @@ -16,6 +16,9 @@ __docformat__ = "restructuredtext en" # Imports #----------------------------------------------------------------------------- +# Tell nose to skip the testing of this module +__test__ = {} + import copy, time from types import FunctionType diff --git a/IPython/kernel/taskclient.py b/IPython/kernel/taskclient.py index dc95418..69225fb 100644 --- a/IPython/kernel/taskclient.py +++ b/IPython/kernel/taskclient.py @@ -49,7 +49,7 @@ class BlockingTaskClient(object): """ implements( - IBlockingTaskClient, + IBlockingTaskClient, ITaskMapperFactory, IMapper, ITaskParallelDecorator @@ -62,7 +62,7 @@ class BlockingTaskClient(object): def run(self, task, block=False): """Run a task on the `TaskController`. - See the documentation of the `MapTask` and `StringTask` classes for + See the documentation of the `MapTask` and `StringTask` classes for details on how to build a task of different types. :Parameters: diff --git a/IPython/kernel/tests/engineservicetest.py b/IPython/kernel/tests/engineservicetest.py index 9544db3..ebb0587 100644 --- a/IPython/kernel/tests/engineservicetest.py +++ b/IPython/kernel/tests/engineservicetest.py @@ -363,7 +363,8 @@ class IEnginePropertiesTestCase(object): p = get_engine(%s).properties"""%self.engine.id d = self.engine.execute(s) d.addCallback(lambda r: self.engine.execute("p['a'] = lambda _:None")) - d = self.assertDeferredRaises(d, error.InvalidProperty) + d.addErrback(lambda f: self.assertRaises(error.InvalidProperty, + f.raiseException)) d.addCallback(lambda r: self.engine.execute("p['a'] = range(5)")) d.addCallback(lambda r: self.engine.execute("p['a'].append(5)")) d.addCallback(lambda r: self.engine.get_properties('a')) diff --git a/IPython/kernel/tests/test_contexts.py b/IPython/kernel/tests/test_contexts.py index 2ef2dec..fe726c3 100644 --- a/IPython/kernel/tests/test_contexts.py +++ b/IPython/kernel/tests/test_contexts.py @@ -1,3 +1,6 @@ +# Tell nose to skip this module +__test__ = {} + #from __future__ import with_statement # XXX This file is currently disabled to preserve 2.4 compatibility. diff --git a/IPython/kernel/tests/test_controllerservice.py b/IPython/kernel/tests/test_controllerservice.py index 21f30b3..749fac7 100644 --- a/IPython/kernel/tests/test_controllerservice.py +++ b/IPython/kernel/tests/test_controllerservice.py @@ -23,15 +23,15 @@ __docformat__ = "restructuredtext en" # Imports #------------------------------------------------------------------------------- -try: - from twisted.application.service import IService - from IPython.kernel.controllerservice import ControllerService - from IPython.kernel.tests import multienginetest as met - from controllertest import IControllerCoreTestCase - from IPython.testing.util import DeferredTestCase -except ImportError: - import nose - raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap") +# Tell nose to skip this module +__test__ = {} + +from twisted.application.service import IService +from IPython.kernel.controllerservice import ControllerService +from IPython.kernel.tests import multienginetest as met +from controllertest import IControllerCoreTestCase +from IPython.testing.util import DeferredTestCase + class BasicControllerServiceTest(DeferredTestCase, IControllerCoreTestCase): diff --git a/IPython/kernel/tests/test_enginefc.py b/IPython/kernel/tests/test_enginefc.py index b8d0caf..502b421 100644 --- a/IPython/kernel/tests/test_enginefc.py +++ b/IPython/kernel/tests/test_enginefc.py @@ -15,30 +15,29 @@ __docformat__ = "restructuredtext en" # Imports #------------------------------------------------------------------------------- -try: - from twisted.python import components - from twisted.internet import reactor, defer - from twisted.spread import pb - from twisted.internet.base import DelayedCall - DelayedCall.debug = True +# Tell nose to skip this module +__test__ = {} - import zope.interface as zi +from twisted.python import components +from twisted.internet import reactor, defer +from twisted.spread import pb +from twisted.internet.base import DelayedCall +DelayedCall.debug = True - from IPython.kernel.fcutil import Tub, UnauthenticatedTub - from IPython.kernel import engineservice as es - from IPython.testing.util import DeferredTestCase - from IPython.kernel.controllerservice import IControllerBase - from IPython.kernel.enginefc import FCRemoteEngineRefFromService, IEngineBase - from IPython.kernel.engineservice import IEngineQueued - from IPython.kernel.engineconnector import EngineConnector - - from IPython.kernel.tests.engineservicetest import \ - IEngineCoreTestCase, \ - IEngineSerializedTestCase, \ - IEngineQueuedTestCase -except ImportError: - import nose - raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap") +import zope.interface as zi + +from IPython.kernel.fcutil import Tub, UnauthenticatedTub +from IPython.kernel import engineservice as es +from IPython.testing.util import DeferredTestCase +from IPython.kernel.controllerservice import IControllerBase +from IPython.kernel.enginefc import FCRemoteEngineRefFromService, IEngineBase +from IPython.kernel.engineservice import IEngineQueued +from IPython.kernel.engineconnector import EngineConnector + +from IPython.kernel.tests.engineservicetest import \ + IEngineCoreTestCase, \ + IEngineSerializedTestCase, \ + IEngineQueuedTestCase class EngineFCTest(DeferredTestCase, diff --git a/IPython/kernel/tests/test_engineservice.py b/IPython/kernel/tests/test_engineservice.py index 22a47eb..a455030 100644 --- a/IPython/kernel/tests/test_engineservice.py +++ b/IPython/kernel/tests/test_engineservice.py @@ -23,20 +23,19 @@ __docformat__ = "restructuredtext en" # Imports #------------------------------------------------------------------------------- -try: - from twisted.internet import defer - from twisted.application.service import IService - - from IPython.kernel import engineservice as es - from IPython.testing.util import DeferredTestCase - from IPython.kernel.tests.engineservicetest import \ - IEngineCoreTestCase, \ - IEngineSerializedTestCase, \ - IEngineQueuedTestCase, \ - IEnginePropertiesTestCase -except ImportError: - import nose - raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap") +# Tell nose to skip this module +__test__ = {} + +from twisted.internet import defer +from twisted.application.service import IService + +from IPython.kernel import engineservice as es +from IPython.testing.util import DeferredTestCase +from IPython.kernel.tests.engineservicetest import \ + IEngineCoreTestCase, \ + IEngineSerializedTestCase, \ + IEngineQueuedTestCase, \ + IEnginePropertiesTestCase class BasicEngineServiceTest(DeferredTestCase, diff --git a/IPython/kernel/tests/test_multiengine.py b/IPython/kernel/tests/test_multiengine.py index 82bf41b..3fe397f 100644 --- a/IPython/kernel/tests/test_multiengine.py +++ b/IPython/kernel/tests/test_multiengine.py @@ -4,29 +4,28 @@ __docformat__ = "restructuredtext en" -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- # Copyright (C) 2008 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 -#------------------------------------------------------------------------------- - -try: - from twisted.internet import defer - from IPython.testing.util import DeferredTestCase - from IPython.kernel.controllerservice import ControllerService - from IPython.kernel import multiengine as me - from IPython.kernel.tests.multienginetest import (IMultiEngineTestCase, - ISynchronousMultiEngineTestCase) -except ImportError: - import nose - raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap") - - +#----------------------------------------------------------------------------- + +# Tell nose to skip this module +__test__ = {} + +from twisted.internet import defer +from IPython.testing.util import DeferredTestCase +from IPython.kernel.controllerservice import ControllerService +from IPython.kernel import multiengine as me +from IPython.kernel.tests.multienginetest import (IMultiEngineTestCase, + ISynchronousMultiEngineTestCase) + + class BasicMultiEngineTestCase(DeferredTestCase, IMultiEngineTestCase): def setUp(self): diff --git a/IPython/kernel/tests/test_multienginefc.py b/IPython/kernel/tests/test_multienginefc.py index de24c4c..97d69e1 100644 --- a/IPython/kernel/tests/test_multienginefc.py +++ b/IPython/kernel/tests/test_multienginefc.py @@ -14,25 +14,25 @@ __docformat__ = "restructuredtext en" # Imports #------------------------------------------------------------------------------- -try: - from twisted.internet import defer, reactor +# Tell nose to skip this module +__test__ = {} + +from twisted.internet import defer, reactor + +from IPython.kernel.fcutil import Tub, UnauthenticatedTub + +from IPython.testing.util import DeferredTestCase +from IPython.kernel.controllerservice import ControllerService +from IPython.kernel.multiengine import IMultiEngine +from IPython.kernel.tests.multienginetest import IFullSynchronousMultiEngineTestCase +from IPython.kernel.multienginefc import IFCSynchronousMultiEngine +from IPython.kernel import multiengine as me +from IPython.kernel.clientconnector import ClientConnector +from IPython.kernel.parallelfunction import ParallelFunction +from IPython.kernel.error import CompositeError +from IPython.kernel.util import printer - from IPython.kernel.fcutil import Tub, UnauthenticatedTub - from IPython.testing.util import DeferredTestCase - from IPython.kernel.controllerservice import ControllerService - from IPython.kernel.multiengine import IMultiEngine - from IPython.kernel.tests.multienginetest import IFullSynchronousMultiEngineTestCase - from IPython.kernel.multienginefc import IFCSynchronousMultiEngine - from IPython.kernel import multiengine as me - from IPython.kernel.clientconnector import ClientConnector - from IPython.kernel.parallelfunction import ParallelFunction - from IPython.kernel.error import CompositeError - from IPython.kernel.util import printer -except ImportError: - import nose - raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap") - def _raise_it(f): try: f.raiseException() diff --git a/IPython/kernel/tests/test_newserialized.py b/IPython/kernel/tests/test_newserialized.py index 747b694..57b6d7b 100644 --- a/IPython/kernel/tests/test_newserialized.py +++ b/IPython/kernel/tests/test_newserialized.py @@ -4,36 +4,36 @@ __docformat__ = "restructuredtext en" -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- # Copyright (C) 2008 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 -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- -try: - import zope.interface as zi - from twisted.trial import unittest - from IPython.testing.util import DeferredTestCase +# Tell nose to skip this module +__test__ = {} - from IPython.kernel.newserialized import \ - ISerialized, \ - IUnSerialized, \ - Serialized, \ - UnSerialized, \ - SerializeIt, \ - UnSerializeIt -except ImportError: - import nose - raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap") +import zope.interface as zi +from twisted.trial import unittest +from IPython.testing.util import DeferredTestCase -#------------------------------------------------------------------------------- +from IPython.kernel.newserialized import \ + ISerialized, \ + IUnSerialized, \ + Serialized, \ + UnSerialized, \ + SerializeIt, \ + UnSerializeIt + + +#----------------------------------------------------------------------------- # Tests -#------------------------------------------------------------------------------- +#----------------------------------------------------------------------------- class SerializedTestCase(unittest.TestCase): diff --git a/IPython/kernel/tests/test_pendingdeferred.py b/IPython/kernel/tests/test_pendingdeferred.py index 73d3b84..e12b56b 100644 --- a/IPython/kernel/tests/test_pendingdeferred.py +++ b/IPython/kernel/tests/test_pendingdeferred.py @@ -16,18 +16,18 @@ __docformat__ = "restructuredtext en" # Imports #------------------------------------------------------------------------------- -try: - from twisted.internet import defer - from twisted.python import failure - - from IPython.testing.util import DeferredTestCase - import IPython.kernel.pendingdeferred as pd - from IPython.kernel import error - from IPython.kernel.util import printer -except ImportError: - import nose - raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap") - +# Tell nose to skip this module +__test__ = {} + +from twisted.internet import defer +from twisted.python import failure + +from IPython.testing.util import DeferredTestCase +import IPython.kernel.pendingdeferred as pd +from IPython.kernel import error +from IPython.kernel.util import printer + + class Foo(object): def bar(self, bahz): diff --git a/IPython/kernel/tests/test_task.py b/IPython/kernel/tests/test_task.py index face815..7060579 100644 --- a/IPython/kernel/tests/test_task.py +++ b/IPython/kernel/tests/test_task.py @@ -15,19 +15,19 @@ __docformat__ = "restructuredtext en" # Imports #------------------------------------------------------------------------------- -try: - import time - - from twisted.internet import defer - from twisted.trial import unittest - - from IPython.kernel import task, controllerservice as cs, engineservice as es - from IPython.kernel.multiengine import IMultiEngine - from IPython.testing.util import DeferredTestCase - from IPython.kernel.tests.tasktest import ITaskControllerTestCase -except ImportError: - import nose - raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap") +# Tell nose to skip this module +__test__ = {} + +import time + +from twisted.internet import defer +from twisted.trial import unittest + +from IPython.kernel import task, controllerservice as cs, engineservice as es +from IPython.kernel.multiengine import IMultiEngine +from IPython.testing.util import DeferredTestCase +from IPython.kernel.tests.tasktest import ITaskControllerTestCase + #------------------------------------------------------------------------------- # Tests diff --git a/IPython/kernel/tests/test_taskfc.py b/IPython/kernel/tests/test_taskfc.py index 266c7fa..371d800 100644 --- a/IPython/kernel/tests/test_taskfc.py +++ b/IPython/kernel/tests/test_taskfc.py @@ -14,27 +14,26 @@ __docformat__ = "restructuredtext en" # Imports #------------------------------------------------------------------------------- -try: - import time +# Tell nose to skip this module +__test__ = {} - from twisted.internet import defer, reactor +import time - from IPython.kernel.fcutil import Tub, UnauthenticatedTub +from twisted.internet import defer, reactor - from IPython.kernel import task as taskmodule - from IPython.kernel import controllerservice as cs - import IPython.kernel.multiengine as me - from IPython.testing.util import DeferredTestCase - from IPython.kernel.multienginefc import IFCSynchronousMultiEngine - from IPython.kernel.taskfc import IFCTaskController - from IPython.kernel.util import printer - from IPython.kernel.tests.tasktest import ITaskControllerTestCase - from IPython.kernel.clientconnector import ClientConnector - from IPython.kernel.error import CompositeError - from IPython.kernel.parallelfunction import ParallelFunction -except ImportError: - import nose - raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap") +from IPython.kernel.fcutil import Tub, UnauthenticatedTub + +from IPython.kernel import task as taskmodule +from IPython.kernel import controllerservice as cs +import IPython.kernel.multiengine as me +from IPython.testing.util import DeferredTestCase +from IPython.kernel.multienginefc import IFCSynchronousMultiEngine +from IPython.kernel.taskfc import IFCTaskController +from IPython.kernel.util import printer +from IPython.kernel.tests.tasktest import ITaskControllerTestCase +from IPython.kernel.clientconnector import ClientConnector +from IPython.kernel.error import CompositeError +from IPython.kernel.parallelfunction import ParallelFunction #------------------------------------------------------------------------------- diff --git a/IPython/kernel/tests/test_twistedutil.py b/IPython/kernel/tests/test_twistedutil.py new file mode 100644 index 0000000..9838ff3 --- /dev/null +++ b/IPython/kernel/tests/test_twistedutil.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# encoding: utf-8 + +#----------------------------------------------------------------------------- +# Copyright (C) 2008 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 +#----------------------------------------------------------------------------- + +# Tell nose to skip this module +__test__ = {} + +import tempfile +import os, sys + +from twisted.internet import reactor +from twisted.trial import unittest + +from IPython.kernel.error import FileTimeoutError +from IPython.kernel.twistedutil import wait_for_file + +#----------------------------------------------------------------------------- +# Tests +#----------------------------------------------------------------------------- + +class TestWaitForFile(unittest.TestCase): + + def test_delay(self): + filename = tempfile.mktemp() + def _create_file(): + open(filename,'w').write('####') + dcall = reactor.callLater(0.5, _create_file) + d = wait_for_file(filename,delay=0.1) + d.addCallback(lambda r: self.assert_(r)) + def _cancel_dcall(r): + if dcall.active(): + dcall.cancel() + d.addCallback(_cancel_dcall) + return d + + def test_timeout(self): + filename = tempfile.mktemp() + d = wait_for_file(filename,delay=0.1,max_tries=1) + d.addErrback(lambda f: self.assertRaises(FileTimeoutError,f.raiseException)) + return d + \ No newline at end of file diff --git a/IPython/kernel/twistedutil.py b/IPython/kernel/twistedutil.py index 6956d38..33dc429 100644 --- a/IPython/kernel/twistedutil.py +++ b/IPython/kernel/twistedutil.py @@ -16,12 +16,15 @@ __docformat__ = "restructuredtext en" # Imports #------------------------------------------------------------------------------- +import os, sys import threading, Queue, atexit -import twisted +import twisted from twisted.internet import defer, reactor from twisted.python import log, failure +from IPython.kernel.error import FileTimeoutError + #------------------------------------------------------------------------------- # Classes related to twisted and threads #------------------------------------------------------------------------------- @@ -204,3 +207,43 @@ class DeferredList(defer.Deferred): result = None return result + + +def wait_for_file(filename, delay=0.1, max_tries=10): + """Wait (poll) for a file to be created. + + This method returns a Deferred that will fire when a file exists. It + works by polling os.path.isfile in time intervals specified by the + delay argument. If `max_tries` is reached, it will errback with a + `FileTimeoutError`. + + Parameters + ---------- + filename : str + The name of the file to wait for. + delay : float + The time to wait between polls. + max_tries : int + The max number of attempts before raising `FileTimeoutError` + + Returns + ------- + d : Deferred + A Deferred instance that will fire when the file exists. + """ + + d = defer.Deferred() + + def _test_for_file(filename, attempt=0): + if attempt >= max_tries: + d.errback(FileTimeoutError( + 'timeout waiting for file to be created: %s' % filename + )) + else: + if os.path.isfile(filename): + d.callback(True) + else: + reactor.callLater(delay, _test_for_file, filename, attempt+1) + + _test_for_file(filename) + return d diff --git a/IPython/platutils.py b/IPython/platutils.py index 6d1d94c..561e68d 100644 --- a/IPython/platutils.py +++ b/IPython/platutils.py @@ -61,6 +61,41 @@ def set_term_title(title): _platutils.set_term_title(title) +class FindCmdError(Exception): + pass + +def find_cmd(cmd): + """Find full path to executable cmd in a cross platform manner. + + This function tries to determine the full path to a command line program + using `which` on Unix/Linux/OS X and `win32api` on Windows. Most of the + time it will use the version that is first on the users `PATH`. If + cmd is `python` return `sys.executable`. + + Parameters + ---------- + cmd : str + The command line program to look for. + """ + if cmd == 'python': + return sys.executable + try: + path = _platutils.find_cmd(cmd) + except: + raise FindCmdError('command could not be found: %s' % cmd) + # which returns empty if not found + if path == '': + raise FindCmdError('command could not be found: %s' % cmd) + return path + +def get_long_path_name(path): + """Expand a path into its long form. + + On Windows this expands any ~ in the paths. On other platforms, it is + a null operation. + """ + return _platutils.get_long_path_name(path) + #----------------------------------------------------------------------------- # Deprecated functions #----------------------------------------------------------------------------- diff --git a/IPython/platutils_dummy.py b/IPython/platutils_dummy.py index 1066ac1..0cb8965 100644 --- a/IPython/platutils_dummy.py +++ b/IPython/platutils_dummy.py @@ -23,3 +23,11 @@ ignore_termtitle = True def set_term_title(*args,**kw): """Dummy no-op.""" pass + +def find_cmd(cmd): + """Find the full path to a command using which.""" + return os.popen('which %s' % cmd).read().strip() + +def get_long_path_name(path): + """Dummy no-op.""" + return path diff --git a/IPython/platutils_posix.py b/IPython/platutils_posix.py index e4d162b..b306479 100644 --- a/IPython/platutils_posix.py +++ b/IPython/platutils_posix.py @@ -30,3 +30,11 @@ if os.environ.get('TERM','') == 'xterm': set_term_title = _set_term_title_xterm else: set_term_title = _dummy_op + +def find_cmd(cmd): + """Find the full path to a command using which.""" + return os.popen('which %s' % cmd).read().strip() + +def get_long_path_name(path): + """Dummy no-op.""" + return path diff --git a/IPython/platutils_win32.py b/IPython/platutils_win32.py index 36f9b31..66ae27f 100644 --- a/IPython/platutils_win32.py +++ b/IPython/platutils_win32.py @@ -41,3 +41,42 @@ except ImportError: if ret: # non-zero return code signals error, don't try again ignore_termtitle = True + +def find_cmd(cmd): + """Find the full path to a .bat or .exe using the win32api module.""" + try: + import win32api + except ImportError: + raise ImportError('you need to have pywin32 installed for this to work') + else: + try: + (path, offest) = win32api.SearchPath(os.environ['PATH'],cmd + '.exe') + except: + (path, offset) = win32api.SearchPath(os.environ['PATH'],cmd + '.bat') + return path + + +def get_long_path_name(path): + """Get a long path name (expand ~) on Windows using ctypes. + + Examples + -------- + + >>> get_long_path_name('c:\\docume~1') + u'c:\\\\Documents and Settings' + + """ + try: + import ctypes + except ImportError: + raise ImportError('you need to have ctypes installed for this to work') + _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW + _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p, + ctypes.c_uint ] + + buf = ctypes.create_unicode_buffer(260) + rv = _GetLongPathName(path, buf, 260) + if rv == 0 or rv > 260: + return path + else: + return buf.value diff --git a/IPython/testing/decorators.py b/IPython/testing/decorators.py index 5d588a0..10eefd4 100644 --- a/IPython/testing/decorators.py +++ b/IPython/testing/decorators.py @@ -123,10 +123,10 @@ def skipif(skip_condition, msg=None): Parameters ---------- skip_condition : bool or callable. - Flag to determine whether to skip test. If the condition is a - callable, it is used at runtime to dynamically make the decision. This - is useful for tests that may require costly imports, to delay the cost - until the test suite is actually executed. + Flag to determine whether to skip test. If the condition is a + callable, it is used at runtime to dynamically make the decision. This + is useful for tests that may require costly imports, to delay the cost + until the test suite is actually executed. msg : string Message to give on raising a SkipTest exception diff --git a/IPython/testing/decorators_numpy.py b/IPython/testing/decorators_numpy.py index e6a56cb..792dd00 100644 --- a/IPython/testing/decorators_numpy.py +++ b/IPython/testing/decorators_numpy.py @@ -29,13 +29,6 @@ def setastest(tf=True): tf : bool If True specifies this is a test, not a test otherwise - e.g - >>> from numpy.testing.decorators import setastest - >>> @setastest(False) - ... def func_with_test_in_name(arg1, arg2): pass - ... - >>> - This decorator cannot use the nose namespace, because it can be called from a non-test module. See also istest and nottest in nose.tools diff --git a/IPython/testing/decorators_trial.py b/IPython/testing/decorators_trial.py new file mode 100644 index 0000000..231054b --- /dev/null +++ b/IPython/testing/decorators_trial.py @@ -0,0 +1,132 @@ +# encoding: utf-8 +""" +Testing related decorators for use with twisted.trial. + +The decorators in this files are designed to follow the same API as those +in the decorators module (in this same directory). But they can be used +with twisted.trial +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 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 os +import sys + +from IPython.testing.decorators import make_label_dec + +#----------------------------------------------------------------------------- +# Testing decorators +#----------------------------------------------------------------------------- + + +def skipif(skip_condition, msg=None): + """Create a decorator that marks a test function for skipping. + + The is a decorator factory that returns a decorator that will + conditionally skip a test based on the value of skip_condition. The + skip_condition argument can either be a boolean or a callable that returns + a boolean. + + Parameters + ---------- + skip_condition : boolean or callable + If this evaluates to True, the test is skipped. + msg : str + The message to print if the test is skipped. + + Returns + ------- + decorator : function + The decorator function that can be applied to the test function. + """ + + def skip_decorator(f): + + # Allow for both boolean or callable skip conditions. + if callable(skip_condition): + skip_val = lambda : skip_condition() + else: + skip_val = lambda : skip_condition + + if msg is None: + out = 'Test skipped due to test condition.' + else: + out = msg + final_msg = "Skipping test: %s. %s" % (f.__name__,out) + + if skip_val(): + f.skip = final_msg + + return f + return skip_decorator + + +def skip(msg=None): + """Create a decorator that marks a test function for skipping. + + This is a decorator factory that returns a decorator that will cause + tests to be skipped. + + Parameters + ---------- + msg : str + Optional message to be added. + + Returns + ------- + decorator : function + Decorator, which, when applied to a function, sets the skip + attribute of the function causing `twisted.trial` to skip it. + """ + + return skipif(True,msg) + + +def numpy_not_available(): + """Can numpy be imported? Returns true if numpy does NOT import. + + This is used to make a decorator to skip tests that require numpy to be + available, but delay the 'import numpy' to test execution time. + """ + try: + import numpy + np_not_avail = False + except ImportError: + np_not_avail = True + + return np_not_avail + +#----------------------------------------------------------------------------- +# Decorators for public use +#----------------------------------------------------------------------------- + +# Decorators to skip certain tests on specific platforms. +skip_win32 = skipif(sys.platform == 'win32', + "This test does not run under Windows") +skip_linux = skipif(sys.platform == 'linux2', + "This test does not run under Linux") +skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X") + +# Decorators to skip tests if not on specific platforms. +skip_if_not_win32 = skipif(sys.platform != 'win32', + "This test only runs under Windows") +skip_if_not_linux = skipif(sys.platform != 'linux2', + "This test only runs under Linux") +skip_if_not_osx = skipif(sys.platform != 'darwin', + "This test only runs under OSX") + +# Other skip decorators +skipif_not_numpy = skipif(numpy_not_available,"This test requires numpy") + +skipknownfailure = skip('This test is known to fail') + + diff --git a/IPython/testing/iptest.py b/IPython/testing/iptest.py index 50f9bee..02aa77a 100644 --- a/IPython/testing/iptest.py +++ b/IPython/testing/iptest.py @@ -1,54 +1,121 @@ # -*- coding: utf-8 -*- """IPython Test Suite Runner. -This module provides a main entry point to a user script to test IPython itself -from the command line. The main() routine can be used in a similar manner to -the ``nosetests`` script, and it takes similar arguments, but if no arguments -are given it defaults to testing all of IPython. This should be preferred to -using plain ``nosetests`` because a number of nose plugins necessary to test -IPython correctly are automatically configured by this code. +This module provides a main entry point to a user script to test IPython +itself from the command line. There are two ways of running this script: + +1. With the syntax `iptest all`. This runs our entire test suite by + calling this script (with different arguments) or trial recursively. This + causes modules and package to be tested in different processes, using nose + or trial where appropriate. +2. With the regular nose syntax, like `iptest -vvs IPython`. In this form + the script simply calls nose, but with special command line flags and + plugins loaded. + +For now, this script requires that both nose and twisted are installed. This +will change in the future. """ #----------------------------------------------------------------------------- # Module imports #----------------------------------------------------------------------------- -# stdlib +import os +import os.path as path import sys +import subprocess +import time import warnings -# third-party import nose.plugins.builtin from nose.core import TestProgram -# Our own imports +from IPython.platutils import find_cmd from IPython.testing.plugin.ipdoctest import IPythonDoctest +pjoin = path.join + #----------------------------------------------------------------------------- -# Constants and globals +# Logic for skipping doctests #----------------------------------------------------------------------------- +def test_for(mod): + """Test to see if mod is importable.""" + try: + __import__(mod) + except ImportError: + return False + else: + return True + +have_curses = test_for('_curses') +have_wx = test_for('wx') +have_zi = test_for('zope.interface') +have_twisted = test_for('twisted') +have_foolscap = test_for('foolscap') +have_objc = test_for('objc') +have_pexpect = test_for('pexpect') + # For the IPythonDoctest plugin, we need to exclude certain patterns that cause # testing problems. We should strive to minimize the number of skipped # modules, since this means untested code. As the testing machinery # solidifies, this list should eventually become empty. -EXCLUDE = ['IPython/external/', - 'IPython/platutils_win32', - 'IPython/frontend/cocoa', - 'IPython_doctest_plugin', - 'IPython/Gnuplot', - 'IPython/Extensions/ipy_', - 'IPython/Extensions/clearcmd', - 'IPython/Extensions/PhysicalQIn', - 'IPython/Extensions/scitedirector', +EXCLUDE = [pjoin('IPython', 'external'), + pjoin('IPython', 'frontend', 'process', 'winprocess.py'), + pjoin('IPython_doctest_plugin'), + pjoin('IPython', 'Gnuplot'), + pjoin('IPython', 'Extensions', 'ipy_'), + pjoin('IPython', 'Extensions', 'clearcmd'), + pjoin('IPython', 'Extensions', 'PhysicalQInteractive'), + pjoin('IPython', 'Extensions', 'scitedirector'), + pjoin('IPython', 'Extensions', 'numeric_formats'), + pjoin('IPython', 'testing', 'attic'), + pjoin('IPython', 'testing', 'tutils'), + pjoin('IPython', 'testing', 'tools'), + pjoin('IPython', 'testing', 'mkdoctests') ] +if not have_wx: + EXCLUDE.append(pjoin('IPython', 'Extensions', 'igrid')) + EXCLUDE.append(pjoin('IPython', 'gui')) + EXCLUDE.append(pjoin('IPython', 'frontend', 'wx')) + +if not have_objc: + EXCLUDE.append(pjoin('IPython', 'frontend', 'cocoa')) + +if not have_curses: + EXCLUDE.append(pjoin('IPython', 'Extensions', 'ibrowse')) + +if not sys.platform == 'win32': + EXCLUDE.append(pjoin('IPython', 'platutils_win32')) + +# These have to be skipped on win32 because the use echo, rm, cd, etc. +# See ticket https://bugs.launchpad.net/bugs/366982 +if sys.platform == 'win32': + EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'test_exampleip')) + EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'dtexample')) + +if not os.name == 'posix': + EXCLUDE.append(pjoin('IPython', 'platutils_posix')) + +if not have_pexpect: + EXCLUDE.append(pjoin('IPython', 'irunner')) + +# This is needed for the reg-exp to match on win32 in the ipdoctest plugin. +if sys.platform == 'win32': + EXCLUDE = [s.replace('\\','\\\\') for s in EXCLUDE] + + #----------------------------------------------------------------------------- # Functions and classes #----------------------------------------------------------------------------- -def main(): - """Run the IPython test suite. +def run_iptest(): + """Run the IPython test suite using nose. + + This function is called when this script is **not** called with the form + `iptest all`. It simply calls nose with appropriate command line flags + and accepts all of the standard nose arguments. """ warnings.filterwarnings('ignore', @@ -60,7 +127,7 @@ def main(): # test suite back into working shape. Our nose # plugin needs to be gone through with a fine # toothed comb to find what is causing the problem. - # '--with-ipdoctest', + '--with-ipdoctest', '--ipdoctest-tests','--ipdoctest-extension=txt', '--detailed-errors', @@ -98,3 +165,136 @@ def main(): plugins.append(plug) TestProgram(argv=argv,plugins=plugins) + + +class IPTester(object): + """Call that calls iptest or trial in a subprocess. + """ + def __init__(self,runner='iptest',params=None): + """ """ + if runner == 'iptest': + self.runner = ['iptest','-v'] + else: + self.runner = [find_cmd('trial')] + if params is None: + params = [] + if isinstance(params,str): + params = [params] + self.params = params + + # Assemble call + self.call_args = self.runner+self.params + + def run(self): + """Run the stored commands""" + return subprocess.call(self.call_args) + + +def make_runners(): + """Define the modules and packages that need to be tested. + """ + + # This omits additional top-level modules that should not be doctested. + # XXX: Shell.py is also ommited because of a bug in the skip_doctest + # decorator. See ticket https://bugs.launchpad.net/bugs/366209 + top_mod = \ + ['background_jobs.py', 'ColorANSI.py', 'completer.py', 'ConfigLoader.py', + 'CrashHandler.py', 'Debugger.py', 'deep_reload.py', 'demo.py', + 'DPyGetOpt.py', 'dtutils.py', 'excolors.py', 'FakeModule.py', + 'generics.py', 'genutils.py', 'history.py', 'hooks.py', 'ipapi.py', + 'iplib.py', 'ipmaker.py', 'ipstruct.py', 'Itpl.py', + 'Logger.py', 'macro.py', 'Magic.py', 'OInspect.py', + 'OutputTrap.py', 'platutils.py', 'prefilter.py', 'Prompts.py', + 'PyColorize.py', 'Release.py', 'rlineimpl.py', 'shadowns.py', + 'shellglobals.py', 'strdispatch.py', 'twshell.py', + 'ultraTB.py', 'upgrade_dir.py', 'usage.py', 'wildcard.py', + # See note above for why this is skipped + # 'Shell.py', + 'winconsole.py'] + + if have_pexpect: + top_mod.append('irunner.py') + + if sys.platform == 'win32': + top_mod.append('platutils_win32.py') + elif os.name == 'posix': + top_mod.append('platutils_posix.py') + else: + top_mod.append('platutils_dummy.py') + + # These are tested by nose, so skip IPython.kernel + top_pack = ['config','Extensions','frontend', + 'testing','tests','tools','UserConfig'] + + if have_wx: + top_pack.append('gui') + + modules = ['IPython.%s' % m[:-3] for m in top_mod ] + packages = ['IPython.%s' % m for m in top_pack ] + + # Make runners + runners = dict(zip(top_pack, [IPTester(params=v) for v in packages])) + + # Test IPython.kernel using trial if twisted is installed + if have_zi and have_twisted and have_foolscap: + runners['trial'] = IPTester('trial',['IPython']) + + runners['modules'] = IPTester(params=modules) + + return runners + + +def run_iptestall(): + """Run the entire IPython test suite by calling nose and trial. + + This function constructs :class:`IPTester` instances for all IPython + modules and package and then runs each of them. This causes the modules + and packages of IPython to be tested each in their own subprocess using + nose or twisted.trial appropriately. + """ + runners = make_runners() + # Run all test runners, tracking execution time + failed = {} + t_start = time.time() + for name,runner in runners.iteritems(): + print '*'*77 + print 'IPython test set:',name + res = runner.run() + if res: + failed[name] = res + t_end = time.time() + t_tests = t_end - t_start + nrunners = len(runners) + nfail = len(failed) + # summarize results + print + print '*'*77 + print 'Ran %s test sets in %.3fs' % (nrunners, t_tests) + print + if not failed: + print 'OK' + else: + # If anything went wrong, point out what command to rerun manually to + # see the actual errors and individual summary + print 'ERROR - %s out of %s test sets failed.' % (nfail, nrunners) + for name in failed: + failed_runner = runners[name] + print '-'*40 + print 'Runner failed:',name + print 'You may wish to rerun this one individually, with:' + print ' '.join(failed_runner.call_args) + print + + +def main(): + if len(sys.argv) == 1: + run_iptestall() + else: + if sys.argv[1] == 'all': + run_iptestall() + else: + run_iptest() + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/IPython/testing/plugin/ipdoctest.py b/IPython/testing/plugin/ipdoctest.py index 79211de..f63b588 100644 --- a/IPython/testing/plugin/ipdoctest.py +++ b/IPython/testing/plugin/ipdoctest.py @@ -59,6 +59,19 @@ log = logging.getLogger(__name__) # machinery into a fit. This code should be considered a gross hack, but it # gets the job done. +def default_argv(): + """Return a valid default argv for creating testing instances of ipython""" + + # Get the install directory for the user configuration and tell ipython to + # use the default profile from there. + from IPython import UserConfig + ipcdir = os.path.dirname(UserConfig.__file__) + #ipconf = os.path.join(ipcdir,'ipy_user_conf.py') + ipconf = os.path.join(ipcdir,'ipythonrc') + #print 'conf:',ipconf # dbg + + return ['--colors=NoColor','--noterm_title','-rcfile=%s' % ipconf] + # Hack to modify the %run command so we can sync the user's namespace with the # test globals. Once we move over to a clean magic system, this will be done @@ -167,10 +180,11 @@ def start_ipython(): _excepthook = sys.excepthook _main = sys.modules.get('__main__') + argv = default_argv() + # Start IPython instance. We customize it to start with minimal frills. user_ns,global_ns = IPython.ipapi.make_user_namespaces(ipnsdict(),dict()) - IPython.Shell.IPShell(['--colors=NoColor','--noterm_title'], - user_ns,global_ns) + IPython.Shell.IPShell(argv,user_ns,global_ns) # Deactivate the various python system hooks added by ipython for # interactive convenience so we don't confuse the doctest system @@ -826,11 +840,11 @@ class ExtensionDoctest(doctests.Doctest): Modified version that accepts extension modules as valid containers for doctests. """ - #print '*** ipdoctest- wantFile:',filename # dbg + # print '*** ipdoctest- wantFile:',filename # dbg for pat in self.exclude_patterns: if pat.search(filename): - #print '###>>> SKIP:',filename # dbg + # print '###>>> SKIP:',filename # dbg return False if is_extension_module(filename): diff --git a/IPython/testing/tests/test_decorators.py b/IPython/testing/tests/test_decorators.py index e91ec73..5f7e688 100644 --- a/IPython/testing/tests/test_decorators.py +++ b/IPython/testing/tests/test_decorators.py @@ -46,7 +46,7 @@ def test_deliberately_broken(): """A deliberately broken test - we want to skip this one.""" 1/0 -@dec.skip('foo') +@dec.skip('Testing the skip decorator') def test_deliberately_broken2(): """Another deliberately broken test - we want to skip this one.""" 1/0 diff --git a/IPython/testing/tests/test_decorators_trial.py b/IPython/testing/tests/test_decorators_trial.py new file mode 100644 index 0000000..6462285 --- /dev/null +++ b/IPython/testing/tests/test_decorators_trial.py @@ -0,0 +1,52 @@ +# encoding: utf-8 +""" +Tests for decorators_trial.py +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 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 +#----------------------------------------------------------------------------- + +# Tell nose to skip this module +__test__ = {} + +import os +import sys + +from twisted.trial import unittest +import IPython.testing.decorators_trial as dec + +#----------------------------------------------------------------------------- +# Tests +#----------------------------------------------------------------------------- + +class TestDecoratorsTrial(unittest.TestCase): + + @dec.skip() + def test_deliberately_broken(self): + """A deliberately broken test - we want to skip this one.""" + 1/0 + + @dec.skip('Testing the skip decorator') + def test_deliberately_broken2(self): + """Another deliberately broken test - we want to skip this one.""" + 1/0 + + @dec.skip_linux + def test_linux(self): + self.assertNotEquals(sys.platform,'linux2',"This test can't run under linux") + + @dec.skip_win32 + def test_win32(self): + self.assertNotEquals(sys.platform,'win32',"This test can't run under windows") + + @dec.skip_osx + def test_osx(self): + self.assertNotEquals(sys.platform,'darwin',"This test can't run under osx") \ No newline at end of file diff --git a/IPython/testing/tests/test_tools.py b/IPython/testing/tests/test_tools.py new file mode 100644 index 0000000..8245c99 --- /dev/null +++ b/IPython/testing/tests/test_tools.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Tests for testing.tools +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 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 os +import sys + +import nose.tools as nt + +from IPython.testing import decorators as dec +from IPython.testing.tools import full_path + +#----------------------------------------------------------------------------- +# Tests +#----------------------------------------------------------------------------- + + +@dec.skip_win32 +def test_full_path_posix(): + spath = '/foo/bar.py' + result = full_path(spath,['a.txt','b.txt']) + nt.assert_equal(result, ['/foo/a.txt', '/foo/b.txt']) + spath = '/foo' + result = full_path(spath,['a.txt','b.txt']) + nt.assert_equal(result, ['/a.txt', '/b.txt']) + result = full_path(spath,'a.txt') + nt.assert_equal(result, ['/a.txt']) + + +@dec.skip_if_not_win32 +def test_full_path_win32(): + spath = 'c:\\foo\\bar.py' + result = full_path(spath,['a.txt','b.txt']) + nt.assert_equal(result, ['c:\\foo\\a.txt', 'c:\\foo\\b.txt']) + spath = 'c:\\foo' + result = full_path(spath,['a.txt','b.txt']) + nt.assert_equal(result, ['c:\\a.txt', 'c:\\b.txt']) + result = full_path(spath,'a.txt') + nt.assert_equal(result, ['c:\\a.txt']) \ No newline at end of file diff --git a/IPython/testing/tools.py b/IPython/testing/tools.py index b9f52f1..c43754c 100644 --- a/IPython/testing/tools.py +++ b/IPython/testing/tools.py @@ -26,15 +26,13 @@ Authors # Required modules and packages #----------------------------------------------------------------------------- -# Standard Python lib import os import sys -# Third-party import nose.tools as nt -# From this project from IPython.tools import utils +from IPython.testing import decorators as dec #----------------------------------------------------------------------------- # Globals @@ -55,6 +53,7 @@ for _x in [a for a in dir(nt) if a.startswith('assert')]: # Functions and classes #----------------------------------------------------------------------------- + def full_path(startPath,files): """Make full paths for all the listed files, based on startPath. @@ -62,7 +61,8 @@ def full_path(startPath,files): used with a script's __file__ variable as startPath. The base of startPath is then prepended to all the listed files, forming the output list. - :Parameters: + Parameters + ---------- startPath : string Initial path to use as the base for the results. This path is split using os.path.split() and only its first component is kept. @@ -70,7 +70,8 @@ def full_path(startPath,files): files : string or list One or more files. - :Examples: + Examples + -------- >>> full_path('/foo/bar.py',['a.txt','b.txt']) ['/foo/a.txt', '/foo/b.txt'] diff --git a/IPython/tests/test_genutils.py b/IPython/tests/test_genutils.py index ec79815..17d9449 100644 --- a/IPython/tests/test_genutils.py +++ b/IPython/tests/test_genutils.py @@ -210,7 +210,6 @@ def test_get_home_dir_7(): home_dir = genutils.get_home_dir() nt.assert_equal(home_dir, abspath(HOME_TEST_DIR)) - # # Tests for get_ipython_dir # @@ -239,7 +238,6 @@ def test_get_ipython_dir_3(): ipdir = genutils.get_ipython_dir() nt.assert_equal(ipdir, os.path.abspath(os.path.join("someplace", "_ipython"))) - # # Tests for get_security_dir # @@ -249,6 +247,14 @@ def test_get_security_dir(): """Testcase to see if we can call get_security_dir without Exceptions.""" sdir = genutils.get_security_dir() +# +# Tests for get_log_dir +# + +@with_enivronment +def test_get_log_dir(): + """Testcase to see if we can call get_log_dir without Exceptions.""" + sdir = genutils.get_log_dir() # # Tests for popkey @@ -289,3 +295,12 @@ def test_popkey_3(): nt.assert_equal(dct, dict(c=3)) nt.assert_equal(genutils.popkey(dct, "c"), 3) nt.assert_equal(dct, dict()) + + +def test_filefind(): + """Various tests for filefind""" + f = tempfile.NamedTemporaryFile() + print 'fname:',f.name + alt_dirs = genutils.get_ipython_dir() + t = genutils.filefind(f.name,alt_dirs) + print 'found:',t diff --git a/IPython/tests/test_iplib.py b/IPython/tests/test_iplib.py index 0924e1b..723343c 100644 --- a/IPython/tests/test_iplib.py +++ b/IPython/tests/test_iplib.py @@ -13,7 +13,7 @@ import tempfile import nose.tools as nt # our own packages -from IPython import iplib +from IPython import ipapi, iplib #----------------------------------------------------------------------------- # Globals @@ -28,7 +28,15 @@ from IPython import iplib # ipapi instance should be read from there, but we also will often need to use # the actual IPython one. -ip = _ip # This is the ipapi instance +# Get the public instance of IPython, and if it's None, make one so we can use +# it for testing +ip = ipapi.get() +if ip is None: + # IPython not running yet, make one from the testing machinery for + # consistency when the test suite is being run via iptest + from IPython.testing.plugin import ipdoctest + ip = ipapi.get() + IP = ip.IP # This is the actual IPython shell 'raw' object. #----------------------------------------------------------------------------- @@ -60,9 +68,13 @@ def test_user_setup(): # Now repeat the operation with a non-existent directory. Check both that # the call succeeds and that the directory is created. tmpdir = tempfile.mktemp(prefix='ipython-test-') + # Use a try with an empty except because try/finally doesn't work with a + # yield in Python 2.4. try: yield user_setup, (tmpdir,''), kw yield os.path.isdir, tmpdir - finally: - # In this case, clean up the temp dir once done - shutil.rmtree(tmpdir) + except: + pass + # Clean up the temp dir once done + shutil.rmtree(tmpdir) + \ No newline at end of file diff --git a/IPython/tests/test_magic.py b/IPython/tests/test_magic.py index 51aef35..77bdd89 100644 --- a/IPython/tests/test_magic.py +++ b/IPython/tests/test_magic.py @@ -3,16 +3,14 @@ Needs to be run by nose (to make ipython session available). """ -# Standard library imports import os import sys import tempfile import types -# Third-party imports import nose.tools as nt -# From our own code +from IPython.platutils import find_cmd, get_long_path_name from IPython.testing import decorators as dec from IPython.testing import tools as tt @@ -60,12 +58,14 @@ def doctest_hist_r(): hist -n -r 2 # random """ - +# This test is known to fail on win32. +# See ticket https://bugs.launchpad.net/bugs/366334 def test_obj_del(): """Test that object's __del__ methods are called on exit.""" test_dir = os.path.dirname(__file__) del_file = os.path.join(test_dir,'obj_del.py') - out = _ip.IP.getoutput('ipython %s' % del_file) + ipython_cmd = find_cmd('ipython') + out = _ip.IP.getoutput('%s %s' % (ipython_cmd, del_file)) nt.assert_equals(out,'obj_del.py: object A deleted') @@ -159,6 +159,7 @@ def doctest_run_ns2(): tclass.py: deleting object: C-first_pass """ +@dec.skip_win32 def doctest_run_builtins(): """Check that %run doesn't damage __builtins__ via a doctest. @@ -204,8 +205,17 @@ class TestMagicRun(object): self.tmpfile = f def run_tmpfile(self): + # This fails on Windows if self.tmpfile.name has spaces or "~" in it. + # See below and ticket https://bugs.launchpad.net/bugs/366353 _ip.magic('run %s' % self.tmpfile.name) + # See https://bugs.launchpad.net/bugs/366353 + @dec.skip_if_not_win32 + def test_run_tempfile_path(self): + tt.assert_equals(True,False,"%run doesn't work with tempfile paths on win32.") + + # See https://bugs.launchpad.net/bugs/366353 + @dec.skip_win32 def test_builtins_id(self): """Check that %run doesn't damage __builtins__ """ @@ -215,6 +225,8 @@ class TestMagicRun(object): bid2 = id(_ip.user_ns['__builtins__']) tt.assert_equals(bid1, bid2) + # See https://bugs.launchpad.net/bugs/366353 + @dec.skip_win32 def test_builtins_type(self): """Check that the type of __builtins__ doesn't change with %run. @@ -225,6 +237,8 @@ class TestMagicRun(object): self.run_tmpfile() tt.assert_equals(type(_ip.user_ns['__builtins__']),type(sys)) + # See https://bugs.launchpad.net/bugs/366353 + @dec.skip_win32 def test_prompts(self): """Test that prompts correctly generate after %run""" self.run_tmpfile() diff --git a/IPython/tests/test_platutils.py b/IPython/tests/test_platutils.py new file mode 100644 index 0000000..36d189f --- /dev/null +++ b/IPython/tests/test_platutils.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Tests for platutils.py +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 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 os +import sys + +import nose.tools as nt + +from IPython.platutils import find_cmd, FindCmdError, get_long_path_name +from IPython.testing import decorators as dec + +#----------------------------------------------------------------------------- +# Tests +#----------------------------------------------------------------------------- + +def test_find_cmd_python(): + """Make sure we find sys.exectable for python.""" + nt.assert_equals(find_cmd('python'), sys.executable) + +@dec.skip_win32 +def test_find_cmd(): + """Make sure we can find the full path to ls.""" + path = find_cmd('ls') + nt.assert_true(path.endswith('ls')) + +@dec.skip_if_not_win32 +def test_find_cmd(): + """Try to find pythonw on Windows.""" + path = find_cmd('pythonw') + nt.assert_true(path.endswith('pythonw.exe')) + +def test_find_cmd_fail(): + """Make sure that FindCmdError is raised if we can't find the cmd.""" + nt.assert_raises(FindCmdError,find_cmd,'asdfasdf') + +@dec.skip_if_not_win32 +def test_get_long_path_name_win32(): + p = get_long_path_name('c:\\docume~1') + nt.assert_equals(p,u'c:\\Documents and Settings') + +@dec.skip_win32 +def test_get_long_path_name(): + p = get_long_path_name('/usr/local') + nt.assert_equals(p,'/usr/local') + diff --git a/docs/autogen_api.py b/docs/autogen_api.py index c7ab055..c7c54de 100755 --- a/docs/autogen_api.py +++ b/docs/autogen_api.py @@ -25,6 +25,7 @@ if __name__ == '__main__': r'\.cocoa', r'\.ipdoctest', r'\.Gnuplot', + r'\.frontend.process.winprocess', ] docwriter.write_api_docs(outdir) docwriter.write_index(outdir, 'gen', diff --git a/docs/examples/kernel/taskreject.py b/docs/examples/kernel/taskreject.py new file mode 100644 index 0000000..deabb02 --- /dev/null +++ b/docs/examples/kernel/taskreject.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# encoding: utf-8 + +""" +A new example showing how to use `TaskRejectError` to handle dependencies +in the IPython task system. + +To run this example, do:: + + $ ipcluster local -n 4 + +Then, in another terminal start up IPython and do:: + + In [0]: %run taskreject.py + + In [1]: mec.execute('run=True', targets=[0,1]) + +After the first command, the scheduler will keep rescheduling the tasks, as +they will fail with `TaskRejectError`. But after the second command, there +are two engines that the tasks can run on. The tasks are quickly funneled +to these engines. + +If you want to see how the controller is scheduling and retrying the tasks +do a `tail -f` on the controller's log file in ~/.ipython/log. +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 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. +#----------------------------------------------------------------------------- + +from IPython.kernel import client +from IPython.kernel import TaskRejectError + +mec = client.MultiEngineClient() +tc = client.TaskClient() + +mec.execute('from IPython.kernel import TaskRejectError') +mec.execute('run = False') + +def map_task(): + if not run: + raise TaskRejectError('task dependency not met') + return 3.0e8 + +task_ids = [] + +for i in range(10): + task = client.MapTask(map_task, retries=20) + task_ids.append(tc.run(task, block=False)) + diff --git a/docs/source/parallel/parallel_process.txt b/docs/source/parallel/parallel_process.txt index 3884d89..c91d5d6 100644 --- a/docs/source/parallel/parallel_process.txt +++ b/docs/source/parallel/parallel_process.txt @@ -302,18 +302,33 @@ The ``--furl-file`` flag works like this:: Make FURL files persistent --------------------------- -At fist glance it may seem that that managing the FURL files is a bit annoying. Going back to the house and key analogy, copying the FURL around each time you start the controller is like having to make a new key every time you want to unlock the door and enter your house. As with your house, you want to be able to create the key (or FURL file) once, and then simply use it at any point in the future. +At fist glance it may seem that that managing the FURL files is a bit +annoying. Going back to the house and key analogy, copying the FURL around +each time you start the controller is like having to make a new key every time +you want to unlock the door and enter your house. As with your house, you want +to be able to create the key (or FURL file) once, and then simply use it at +any point in the future. -This is possible. The only thing you have to do is decide what ports the controller will listen on for the engines and clients. This is done as follows:: +This is possible, but before you do this, you **must** remove any old FURL +files in the :file:`~/.ipython/security` directory. + +.. warning:: + + You **must** remove old FURL files before using persistent FURL files. + +Then, The only thing you have to do is decide what ports the controller will +listen on for the engines and clients. This is done as follows:: $ ipcontroller -r --client-port=10101 --engine-port=10102 -These options also work with all of the various modes of +These options also work with all of the various modes of :command:`ipcluster`:: $ ipcluster local -n 2 -r --client-port=10101 --engine-port=10102 -Then, just copy the furl files over the first time and you are set. You can start and stop the controller and engines any many times as you want in the future, just make sure to tell the controller to use the *same* ports. +Then, just copy the furl files over the first time and you are set. You can +start and stop the controller and engines any many times as you want in the +future, just make sure to tell the controller to use the *same* ports. .. note:: diff --git a/docs/source/parallel/visionhpc.txt b/docs/source/parallel/visionhpc.txt index f0c4e04..7a6b15f 100644 --- a/docs/source/parallel/visionhpc.txt +++ b/docs/source/parallel/visionhpc.txt @@ -2,6 +2,10 @@ IPython/Vision Beam Pattern Demo ================================== +.. note:: + + This page has not been updated to reflect the recent work on ipcluster. + This work makes it much easier to use IPython on a cluster. Installing and testing IPython at OSC systems ============================================= diff --git a/setupbase.py b/setupbase.py index cd75644..9c00a35 100644 --- a/setupbase.py +++ b/setupbase.py @@ -109,7 +109,7 @@ def find_packages(): add_package(packages, 'gui') add_package(packages, 'gui.wx') add_package(packages, 'frontend', tests=True) - add_package(packages, 'frontend._process') + add_package(packages, 'frontend.process') add_package(packages, 'frontend.wx') add_package(packages, 'frontend.cocoa', tests=True) add_package(packages, 'kernel', config=True, tests=True, scripts=True)