##// END OF EJS Templates
Merging upstream
Brian Granger -
r2005:7eb8a846 merge
parent child Browse files
Show More
@@ -0,0 +1,37 b''
1 # encoding: utf-8
2 """
3 Test the LineFrontEnd
4 """
5
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is
12 # in the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 from IPython.frontend.linefrontendbase import LineFrontEndBase
16 from copy import deepcopy
17 import nose.tools as nt
18
19 class ConcreteLineFrontEnd(LineFrontEndBase):
20 """ A concrete class to test the LineFrontEndBase.
21 """
22 def capture_output(self):
23 pass
24
25 def release_output(self):
26 pass
27
28
29 def test_is_complete():
30 """ Tests line completion heuristic.
31 """
32 frontend = ConcreteLineFrontEnd()
33 yield nt.assert_true, not frontend.is_complete('for x in \\')
34 yield nt.assert_true, not frontend.is_complete('for x in (1, ):')
35 yield nt.assert_true, frontend.is_complete('for x in (1, ):\n pass')
36
37
@@ -0,0 +1,51 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2008 The IPython Development Team
6 #
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
10
11 #-----------------------------------------------------------------------------
12 # Imports
13 #-----------------------------------------------------------------------------
14
15 # Tell nose to skip this module
16 __test__ = {}
17
18 import tempfile
19 import os, sys
20
21 from twisted.internet import reactor
22 from twisted.trial import unittest
23
24 from IPython.kernel.error import FileTimeoutError
25 from IPython.kernel.twistedutil import wait_for_file
26
27 #-----------------------------------------------------------------------------
28 # Tests
29 #-----------------------------------------------------------------------------
30
31 class TestWaitForFile(unittest.TestCase):
32
33 def test_delay(self):
34 filename = tempfile.mktemp()
35 def _create_file():
36 open(filename,'w').write('####')
37 dcall = reactor.callLater(0.5, _create_file)
38 d = wait_for_file(filename,delay=0.1)
39 d.addCallback(lambda r: self.assert_(r))
40 def _cancel_dcall(r):
41 if dcall.active():
42 dcall.cancel()
43 d.addCallback(_cancel_dcall)
44 return d
45
46 def test_timeout(self):
47 filename = tempfile.mktemp()
48 d = wait_for_file(filename,delay=0.1,max_tries=1)
49 d.addErrback(lambda f: self.assertRaises(FileTimeoutError,f.raiseException))
50 return d
51 No newline at end of file
@@ -0,0 +1,132 b''
1 # encoding: utf-8
2 """
3 Testing related decorators for use with twisted.trial.
4
5 The decorators in this files are designed to follow the same API as those
6 in the decorators module (in this same directory). But they can be used
7 with twisted.trial
8 """
9
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2008-2009 The IPython Development Team
12 #
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
15 #-----------------------------------------------------------------------------
16
17 #-----------------------------------------------------------------------------
18 # Imports
19 #-----------------------------------------------------------------------------
20
21 import os
22 import sys
23
24 from IPython.testing.decorators import make_label_dec
25
26 #-----------------------------------------------------------------------------
27 # Testing decorators
28 #-----------------------------------------------------------------------------
29
30
31 def skipif(skip_condition, msg=None):
32 """Create a decorator that marks a test function for skipping.
33
34 The is a decorator factory that returns a decorator that will
35 conditionally skip a test based on the value of skip_condition. The
36 skip_condition argument can either be a boolean or a callable that returns
37 a boolean.
38
39 Parameters
40 ----------
41 skip_condition : boolean or callable
42 If this evaluates to True, the test is skipped.
43 msg : str
44 The message to print if the test is skipped.
45
46 Returns
47 -------
48 decorator : function
49 The decorator function that can be applied to the test function.
50 """
51
52 def skip_decorator(f):
53
54 # Allow for both boolean or callable skip conditions.
55 if callable(skip_condition):
56 skip_val = lambda : skip_condition()
57 else:
58 skip_val = lambda : skip_condition
59
60 if msg is None:
61 out = 'Test skipped due to test condition.'
62 else:
63 out = msg
64 final_msg = "Skipping test: %s. %s" % (f.__name__,out)
65
66 if skip_val():
67 f.skip = final_msg
68
69 return f
70 return skip_decorator
71
72
73 def skip(msg=None):
74 """Create a decorator that marks a test function for skipping.
75
76 This is a decorator factory that returns a decorator that will cause
77 tests to be skipped.
78
79 Parameters
80 ----------
81 msg : str
82 Optional message to be added.
83
84 Returns
85 -------
86 decorator : function
87 Decorator, which, when applied to a function, sets the skip
88 attribute of the function causing `twisted.trial` to skip it.
89 """
90
91 return skipif(True,msg)
92
93
94 def numpy_not_available():
95 """Can numpy be imported? Returns true if numpy does NOT import.
96
97 This is used to make a decorator to skip tests that require numpy to be
98 available, but delay the 'import numpy' to test execution time.
99 """
100 try:
101 import numpy
102 np_not_avail = False
103 except ImportError:
104 np_not_avail = True
105
106 return np_not_avail
107
108 #-----------------------------------------------------------------------------
109 # Decorators for public use
110 #-----------------------------------------------------------------------------
111
112 # Decorators to skip certain tests on specific platforms.
113 skip_win32 = skipif(sys.platform == 'win32',
114 "This test does not run under Windows")
115 skip_linux = skipif(sys.platform == 'linux2',
116 "This test does not run under Linux")
117 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
118
119 # Decorators to skip tests if not on specific platforms.
120 skip_if_not_win32 = skipif(sys.platform != 'win32',
121 "This test only runs under Windows")
122 skip_if_not_linux = skipif(sys.platform != 'linux2',
123 "This test only runs under Linux")
124 skip_if_not_osx = skipif(sys.platform != 'darwin',
125 "This test only runs under OSX")
126
127 # Other skip decorators
128 skipif_not_numpy = skipif(numpy_not_available,"This test requires numpy")
129
130 skipknownfailure = skip('This test is known to fail')
131
132
@@ -0,0 +1,94 b''
1 """Tests for the ipdoctest machinery itself.
2
3 Note: in a file named test_X, functions whose only test is their docstring (as
4 a doctest) and which have no test functionality of their own, should be called
5 'doctest_foo' instead of 'test_foo', otherwise they get double-counted (the
6 empty function call is counted as a test, which just inflates tests numbers
7 artificially).
8 """
9
10 def doctest_simple():
11 """ipdoctest must handle simple inputs
12
13 In [1]: 1
14 Out[1]: 1
15
16 In [2]: print 1
17 1
18 """
19
20
21 def doctest_run_builtins():
22 """Check that %run doesn't damage __builtins__ via a doctest.
23
24 This is similar to the test_run_builtins, but I want *both* forms of the
25 test to catch any possible glitches in our testing machinery, since that
26 modifies %run somewhat. So for this, we have both a normal test (below)
27 and a doctest (this one).
28
29 In [1]: import tempfile
30
31 In [3]: f = tempfile.NamedTemporaryFile()
32
33 In [4]: f.write('pass\\n')
34
35 In [5]: f.flush()
36
37 In [7]: %run $f.name
38 """
39
40 def doctest_multiline1():
41 """The ipdoctest machinery must handle multiline examples gracefully.
42
43 In [2]: for i in range(10):
44 ...: print i,
45 ...:
46 0 1 2 3 4 5 6 7 8 9
47 """
48
49
50 def doctest_multiline2():
51 """Multiline examples that define functions and print output.
52
53 In [7]: def f(x):
54 ...: return x+1
55 ...:
56
57 In [8]: f(1)
58 Out[8]: 2
59
60 In [9]: def g(x):
61 ...: print 'x is:',x
62 ...:
63
64 In [10]: g(1)
65 x is: 1
66
67 In [11]: g('hello')
68 x is: hello
69 """
70
71
72 def doctest_multiline3():
73 """Multiline examples with blank lines.
74
75 In [12]: def h(x):
76 ....: if x>1:
77 ....: return x**2
78 ....: # To leave a blank line in the input, you must mark it
79 ....: # with a comment character:
80 ....: #
81 ....: # otherwise the doctest parser gets confused.
82 ....: else:
83 ....: return -1
84 ....:
85
86 In [13]: h(5)
87 Out[13]: 25
88
89 In [14]: h(1)
90 Out[14]: -1
91
92 In [15]: h(0)
93 Out[15]: -1
94 """
@@ -0,0 +1,52 b''
1 # encoding: utf-8
2 """
3 Tests for decorators_trial.py
4 """
5
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2009 The IPython Development Team
8 #
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
12
13 #-----------------------------------------------------------------------------
14 # Imports
15 #-----------------------------------------------------------------------------
16
17 # Tell nose to skip this module
18 __test__ = {}
19
20 import os
21 import sys
22
23 from twisted.trial import unittest
24 import IPython.testing.decorators_trial as dec
25
26 #-----------------------------------------------------------------------------
27 # Tests
28 #-----------------------------------------------------------------------------
29
30 class TestDecoratorsTrial(unittest.TestCase):
31
32 @dec.skip()
33 def test_deliberately_broken(self):
34 """A deliberately broken test - we want to skip this one."""
35 1/0
36
37 @dec.skip('Testing the skip decorator')
38 def test_deliberately_broken2(self):
39 """Another deliberately broken test - we want to skip this one."""
40 1/0
41
42 @dec.skip_linux
43 def test_linux(self):
44 self.assertNotEquals(sys.platform,'linux2',"This test can't run under linux")
45
46 @dec.skip_win32
47 def test_win32(self):
48 self.assertNotEquals(sys.platform,'win32',"This test can't run under windows")
49
50 @dec.skip_osx
51 def test_osx(self):
52 self.assertNotEquals(sys.platform,'darwin',"This test can't run under osx") No newline at end of file
@@ -0,0 +1,52 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 """
4 Tests for testing.tools
5 """
6
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
13
14 #-----------------------------------------------------------------------------
15 # Imports
16 #-----------------------------------------------------------------------------
17
18 import os
19 import sys
20
21 import nose.tools as nt
22
23 from IPython.testing import decorators as dec
24 from IPython.testing.tools import full_path
25
26 #-----------------------------------------------------------------------------
27 # Tests
28 #-----------------------------------------------------------------------------
29
30
31 @dec.skip_win32
32 def test_full_path_posix():
33 spath = '/foo/bar.py'
34 result = full_path(spath,['a.txt','b.txt'])
35 nt.assert_equal(result, ['/foo/a.txt', '/foo/b.txt'])
36 spath = '/foo'
37 result = full_path(spath,['a.txt','b.txt'])
38 nt.assert_equal(result, ['/a.txt', '/b.txt'])
39 result = full_path(spath,'a.txt')
40 nt.assert_equal(result, ['/a.txt'])
41
42
43 @dec.skip_if_not_win32
44 def test_full_path_win32():
45 spath = 'c:\\foo\\bar.py'
46 result = full_path(spath,['a.txt','b.txt'])
47 nt.assert_equal(result, ['c:\\foo\\a.txt', 'c:\\foo\\b.txt'])
48 spath = 'c:\\foo'
49 result = full_path(spath,['a.txt','b.txt'])
50 nt.assert_equal(result, ['c:\\a.txt', 'c:\\b.txt'])
51 result = full_path(spath,'a.txt')
52 nt.assert_equal(result, ['c:\\a.txt']) No newline at end of file
@@ -0,0 +1,89 b''
1 """Generic testing tools that do NOT depend on Twisted.
2
3 In particular, this module exposes a set of top-level assert* functions that
4 can be used in place of nose.tools.assert* in method generators (the ones in
5 nose can not, at least as of nose 0.10.4).
6
7 Note: our testing package contains testing.util, which does depend on Twisted
8 and provides utilities for tests that manage Deferreds. All testing support
9 tools that only depend on nose, IPython or the standard library should go here
10 instead.
11
12
13 Authors
14 -------
15 - Fernando Perez <Fernando.Perez@berkeley.edu>
16 """
17
18 #*****************************************************************************
19 # Copyright (C) 2009 The IPython Development Team
20 #
21 # Distributed under the terms of the BSD License. The full license is in
22 # the file COPYING, distributed as part of this software.
23 #*****************************************************************************
24
25 #-----------------------------------------------------------------------------
26 # Required modules and packages
27 #-----------------------------------------------------------------------------
28
29 import os
30 import sys
31
32 import nose.tools as nt
33
34 from IPython.tools import utils
35 from IPython.testing import decorators as dec
36
37 #-----------------------------------------------------------------------------
38 # Globals
39 #-----------------------------------------------------------------------------
40
41 # Make a bunch of nose.tools assert wrappers that can be used in test
42 # generators. This will expose an assert* function for each one in nose.tools.
43
44 _tpl = """
45 def %(name)s(*a,**kw):
46 return nt.%(name)s(*a,**kw)
47 """
48
49 for _x in [a for a in dir(nt) if a.startswith('assert')]:
50 exec _tpl % dict(name=_x)
51
52 #-----------------------------------------------------------------------------
53 # Functions and classes
54 #-----------------------------------------------------------------------------
55
56
57 def full_path(startPath,files):
58 """Make full paths for all the listed files, based on startPath.
59
60 Only the base part of startPath is kept, since this routine is typically
61 used with a script's __file__ variable as startPath. The base of startPath
62 is then prepended to all the listed files, forming the output list.
63
64 Parameters
65 ----------
66 startPath : string
67 Initial path to use as the base for the results. This path is split
68 using os.path.split() and only its first component is kept.
69
70 files : string or list
71 One or more files.
72
73 Examples
74 --------
75
76 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
77 ['/foo/a.txt', '/foo/b.txt']
78
79 >>> full_path('/foo',['a.txt','b.txt'])
80 ['/a.txt', '/b.txt']
81
82 If a single file is given, the output is still a list:
83 >>> full_path('/foo','a.txt')
84 ['/a.txt']
85 """
86
87 files = utils.list_strings(files)
88 base = os.path.split(startPath)[0]
89 return [ os.path.join(base,f) for f in files ]
@@ -0,0 +1,41 b''
1 """Minimal script to reproduce our nasty reference counting bug.
2
3 The problem is related to https://bugs.launchpad.net/ipython/+bug/269966
4
5 The original fix for that appeared to work, but John D. Hunter found a
6 matplotlib example which, when run twice in a row, would break. The problem
7 were references held by open figures to internals of Tkinter.
8
9 This code reproduces the problem that John saw, without matplotlib.
10
11 This script is meant to be called by other parts of the test suite that call it
12 via %run as if it were executed interactively by the user. As of 2009-04-13,
13 test_magic.py calls it.
14 """
15
16 #-----------------------------------------------------------------------------
17 # Module imports
18 #-----------------------------------------------------------------------------
19 import sys
20
21 from IPython import ipapi
22
23 #-----------------------------------------------------------------------------
24 # Globals
25 #-----------------------------------------------------------------------------
26 ip = ipapi.get()
27
28 if not '_refbug_cache' in ip.user_ns:
29 ip.user_ns['_refbug_cache'] = []
30
31
32 aglobal = 'Hello'
33 def f():
34 return aglobal
35
36 cache = ip.user_ns['_refbug_cache']
37 cache.append(f)
38
39 def call_f():
40 for func in cache:
41 print 'lowercased:',func().lower()
@@ -0,0 +1,17 b''
1 """Tests for the FakeModule objects.
2 """
3
4 import nose.tools as nt
5
6 from IPython.FakeModule import FakeModule, init_fakemod_dict
7
8 # Make a fakemod and check a few properties
9 def test_mk_fakemod():
10 fm = FakeModule()
11 yield nt.assert_true,fm
12 yield nt.assert_true,lambda : hasattr(fm,'__file__')
13
14 def test_mk_fakemod_fromdict():
15 """Test making a FakeModule object with initial data"""
16 fm = FakeModule(dict(hello=True))
17 nt.assert_true(fm.hello)
@@ -0,0 +1,59 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 """
4 Tests for platutils.py
5 """
6
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2009 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
13
14 #-----------------------------------------------------------------------------
15 # Imports
16 #-----------------------------------------------------------------------------
17
18 import os
19 import sys
20
21 import nose.tools as nt
22
23 from IPython.platutils import find_cmd, FindCmdError, get_long_path_name
24 from IPython.testing import decorators as dec
25
26 #-----------------------------------------------------------------------------
27 # Tests
28 #-----------------------------------------------------------------------------
29
30 def test_find_cmd_python():
31 """Make sure we find sys.exectable for python."""
32 nt.assert_equals(find_cmd('python'), sys.executable)
33
34 @dec.skip_win32
35 def test_find_cmd():
36 """Make sure we can find the full path to ls."""
37 path = find_cmd('ls')
38 nt.assert_true(path.endswith('ls'))
39
40 @dec.skip_if_not_win32
41 def test_find_cmd():
42 """Try to find pythonw on Windows."""
43 path = find_cmd('pythonw')
44 nt.assert_true(path.endswith('pythonw.exe'))
45
46 def test_find_cmd_fail():
47 """Make sure that FindCmdError is raised if we can't find the cmd."""
48 nt.assert_raises(FindCmdError,find_cmd,'asdfasdf')
49
50 @dec.skip_if_not_win32
51 def test_get_long_path_name_win32():
52 p = get_long_path_name('c:\\docume~1')
53 nt.assert_equals(p,u'c:\\Documents and Settings')
54
55 @dec.skip_win32
56 def test_get_long_path_name():
57 p = get_long_path_name('/usr/local')
58 nt.assert_equals(p,'/usr/local')
59
@@ -0,0 +1,53 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3
4 """
5 A new example showing how to use `TaskRejectError` to handle dependencies
6 in the IPython task system.
7
8 To run this example, do::
9
10 $ ipcluster local -n 4
11
12 Then, in another terminal start up IPython and do::
13
14 In [0]: %run taskreject.py
15
16 In [1]: mec.execute('run=True', targets=[0,1])
17
18 After the first command, the scheduler will keep rescheduling the tasks, as
19 they will fail with `TaskRejectError`. But after the second command, there
20 are two engines that the tasks can run on. The tasks are quickly funneled
21 to these engines.
22
23 If you want to see how the controller is scheduling and retrying the tasks
24 do a `tail -f` on the controller's log file in ~/.ipython/log.
25 """
26
27 #-----------------------------------------------------------------------------
28 # Copyright (C) 2008-2009 The IPython Development Team
29 #
30 # Distributed under the terms of the BSD License. The full license is in
31 # the file COPYING, distributed as part of this software.
32 #-----------------------------------------------------------------------------
33
34 from IPython.kernel import client
35 from IPython.kernel import TaskRejectError
36
37 mec = client.MultiEngineClient()
38 tc = client.TaskClient()
39
40 mec.execute('from IPython.kernel import TaskRejectError')
41 mec.execute('run = False')
42
43 def map_task():
44 if not run:
45 raise TaskRejectError('task dependency not met')
46 return 3.0e8
47
48 task_ids = []
49
50 for i in range(10):
51 task = client.MapTask(map_task, retries=20)
52 task_ids.append(tc.run(task, block=False))
53
@@ -114,6 +114,11 b' except NameError:'
114 items.reverse()
114 items.reverse()
115 return items
115 return items
116
116
117 try: # Python 2.4 compatibility
118 GeneratorExit
119 except NameError:
120 GeneratorExit = SystemExit
121
117 try:
122 try:
118 import pwd
123 import pwd
119 except ImportError:
124 except ImportError:
@@ -666,7 +671,7 b' def xrepr(item, mode="default"):'
666 try:
671 try:
667 for x in func(mode):
672 for x in func(mode):
668 yield x
673 yield x
669 except (KeyboardInterrupt, SystemExit):
674 except (KeyboardInterrupt, SystemExit, GeneratorExit):
670 raise
675 raise
671 except Exception:
676 except Exception:
672 yield (astyle.style_default, repr(item))
677 yield (astyle.style_default, repr(item))
@@ -840,20 +845,20 b' def upgradexattr(attr):'
840 """
845 """
841 Convert an attribute descriptor string to a real descriptor object.
846 Convert an attribute descriptor string to a real descriptor object.
842
847
843 If attr already is a descriptor object return if unmodified. A
848 If attr already is a descriptor object return it unmodified. A
844 ``SelfDescriptor`` will be returned if ``attr`` is ``None``. ``"foo"``
849 ``SelfDescriptor`` will be returned if ``attr`` is ``None``. ``"foo"``
845 returns an ``AttributeDescriptor`` for the attribute named ``"foo"``.
850 returns an ``AttributeDescriptor`` for the attribute named ``"foo"``.
846 ``"foo()"`` returns a ``MethodDescriptor`` for the method named ``"foo"``.
851 ``"foo()"`` returns a ``MethodDescriptor`` for the method named ``"foo"``.
847 ``"-foo"`` will return an ``IterAttributeDescriptor`` for the attribute
852 ``"-foo"`` will return an ``IterAttributeDescriptor`` for the attribute
848 named ``"foo"`` and ``"-foo()"`` will return an ``IterMethodDescriptor``
853 named ``"foo"`` and ``"-foo()"`` will return an ``IterMethodDescriptor``
849 for the method named ``"foo"``. Furthermore integer will return the appropriate
854 for the method named ``"foo"``. Furthermore integers will return the appropriate
850 ``IndexDescriptor`` and callables will return a ``FunctionDescriptor``.
855 ``IndexDescriptor`` and callables will return a ``FunctionDescriptor``.
851 """
856 """
852 if attr is None:
857 if attr is None:
853 return selfdescriptor
858 return selfdescriptor
854 elif isinstance(attr, Descriptor):
859 elif isinstance(attr, Descriptor):
855 return attr
860 return attr
856 elif isinstance(attr, str):
861 elif isinstance(attr, basestring):
857 if attr.endswith("()"):
862 if attr.endswith("()"):
858 if attr.startswith("-"):
863 if attr.startswith("-"):
859 return IterMethodDescriptor(attr[1:-2])
864 return IterMethodDescriptor(attr[1:-2])
@@ -66,7 +66,7 b' def attr_matches(self, text):'
66 return res
66 return res
67
67
68 def main():
68 def main():
69 import readline
69 import IPython.rlineimpl as readline
70 readline.set_completer_delims(" \n\t")
70 readline.set_completer_delims(" \n\t")
71 # monkeypatch - the code will be folded to normal completer later on
71 # monkeypatch - the code will be folded to normal completer later on
72 import IPython.completer
72 import IPython.completer
@@ -15,6 +15,37 b' sessions.'
15
15
16 import types
16 import types
17
17
18 def init_fakemod_dict(fm,adict=None):
19 """Initialize a FakeModule instance __dict__.
20
21 Kept as a standalone function and not a method so the FakeModule API can
22 remain basically empty.
23
24 This should be considered for private IPython use, used in managing
25 namespaces for %run.
26
27 Parameters
28 ----------
29
30 fm : FakeModule instance
31
32 adict : dict, optional
33 """
34
35 dct = {}
36 # It seems pydoc (and perhaps others) needs any module instance to
37 # implement a __nonzero__ method, so we add it if missing:
38 dct.setdefault('__nonzero__',lambda : True)
39 dct.setdefault('__file__',__file__)
40
41 if adict is not None:
42 dct.update(adict)
43
44 # Hard assignment of the object's __dict__. This is nasty but deliberate.
45 fm.__dict__.clear()
46 fm.__dict__.update(dct)
47
48
18 class FakeModule(types.ModuleType):
49 class FakeModule(types.ModuleType):
19 """Simple class with attribute access to fake a module.
50 """Simple class with attribute access to fake a module.
20
51
@@ -29,14 +60,7 b' class FakeModule(types.ModuleType):'
29
60
30 # tmp to force __dict__ instance creation, else self.__dict__ fails
61 # tmp to force __dict__ instance creation, else self.__dict__ fails
31 self.__iptmp = None
62 self.__iptmp = None
32
33 # It seems pydoc (and perhaps others) needs any module instance to
34 # implement a __nonzero__ method, so we add it if missing:
35 self.__dict__.setdefault('__nonzero__',lambda : True)
36 self.__dict__.setdefault('__file__',__file__)
37
38 # cleanup our temp trick
63 # cleanup our temp trick
39 del self.__iptmp
64 del self.__iptmp
40
65 # Now, initialize the actual data in the instance dict.
41 if adict is not None:
66 init_fakemod_dict(self,adict)
42 self.__dict__.update(adict)
@@ -1584,23 +1584,17 b' Currently the magic system has the following functions:\\n"""'
1584 prog_ns = self.shell.user_ns
1584 prog_ns = self.shell.user_ns
1585 __name__save = self.shell.user_ns['__name__']
1585 __name__save = self.shell.user_ns['__name__']
1586 prog_ns['__name__'] = '__main__'
1586 prog_ns['__name__'] = '__main__'
1587 main_mod = FakeModule(prog_ns)
1587 main_mod = self.shell.new_main_mod(prog_ns)
1588 else:
1588 else:
1589 # Run in a fresh, empty namespace
1589 # Run in a fresh, empty namespace
1590 if opts.has_key('n'):
1590 if opts.has_key('n'):
1591 name = os.path.splitext(os.path.basename(filename))[0]
1591 name = os.path.splitext(os.path.basename(filename))[0]
1592 else:
1592 else:
1593 name = '__main__'
1593 name = '__main__'
1594 main_mod = FakeModule()
1594
1595 main_mod = self.shell.new_main_mod()
1595 prog_ns = main_mod.__dict__
1596 prog_ns = main_mod.__dict__
1596 prog_ns['__name__'] = name
1597 prog_ns['__name__'] = name
1597
1598 # The shell MUST hold a reference to main_mod so after %run exits,
1599 # the python deletion mechanism doesn't zero it out (leaving
1600 # dangling references). However, we should drop old versions of
1601 # main_mod. There is now a proper API to manage this caching in
1602 # the main shell object, we use that.
1603 self.shell.cache_main_mod(main_mod)
1604
1598
1605 # Since '%run foo' emulates 'python foo.py' at the cmd line, we must
1599 # Since '%run foo' emulates 'python foo.py' at the cmd line, we must
1606 # set the __file__ global in the script's namespace
1600 # set the __file__ global in the script's namespace
@@ -1681,7 +1675,7 b' Currently the magic system has the following functions:\\n"""'
1681 exit_ignore=exit_ignore)
1675 exit_ignore=exit_ignore)
1682 t1 = clock2()
1676 t1 = clock2()
1683 t_usr = t1[0]-t0[0]
1677 t_usr = t1[0]-t0[0]
1684 t_sys = t1[1]-t1[1]
1678 t_sys = t1[1]-t0[1]
1685 print "\nIPython CPU timings (estimated):"
1679 print "\nIPython CPU timings (estimated):"
1686 print " User : %10s s." % t_usr
1680 print " User : %10s s." % t_usr
1687 print " System: %10s s." % t_sys
1681 print " System: %10s s." % t_sys
@@ -1693,7 +1687,7 b' Currently the magic system has the following functions:\\n"""'
1693 exit_ignore=exit_ignore)
1687 exit_ignore=exit_ignore)
1694 t1 = clock2()
1688 t1 = clock2()
1695 t_usr = t1[0]-t0[0]
1689 t_usr = t1[0]-t0[0]
1696 t_sys = t1[1]-t1[1]
1690 t_sys = t1[1]-t0[1]
1697 print "\nIPython CPU timings (estimated):"
1691 print "\nIPython CPU timings (estimated):"
1698 print "Total runs performed:",nruns
1692 print "Total runs performed:",nruns
1699 print " Times : %10s %10s" % ('Total','Per run')
1693 print " Times : %10s %10s" % ('Total','Per run')
@@ -1703,13 +1697,28 b' Currently the magic system has the following functions:\\n"""'
1703 else:
1697 else:
1704 # regular execution
1698 # regular execution
1705 runner(filename,prog_ns,prog_ns,exit_ignore=exit_ignore)
1699 runner(filename,prog_ns,prog_ns,exit_ignore=exit_ignore)
1700
1706 if opts.has_key('i'):
1701 if opts.has_key('i'):
1707 self.shell.user_ns['__name__'] = __name__save
1702 self.shell.user_ns['__name__'] = __name__save
1708 else:
1703 else:
1704 # The shell MUST hold a reference to prog_ns so after %run
1705 # exits, the python deletion mechanism doesn't zero it out
1706 # (leaving dangling references).
1707 self.shell.cache_main_mod(prog_ns,filename)
1709 # update IPython interactive namespace
1708 # update IPython interactive namespace
1710 del prog_ns['__name__']
1709 del prog_ns['__name__']
1711 self.shell.user_ns.update(prog_ns)
1710 self.shell.user_ns.update(prog_ns)
1712 finally:
1711 finally:
1712 # It's a bit of a mystery why, but __builtins__ can change from
1713 # being a module to becoming a dict missing some key data after
1714 # %run. As best I can see, this is NOT something IPython is doing
1715 # at all, and similar problems have been reported before:
1716 # http://coding.derkeiler.com/Archive/Python/comp.lang.python/2004-10/0188.html
1717 # Since this seems to be done by the interpreter itself, the best
1718 # we can do is to at least restore __builtins__ for the user on
1719 # exit.
1720 self.shell.user_ns['__builtins__'] = __builtin__
1721
1713 # Ensure key global structures are restored
1722 # Ensure key global structures are restored
1714 sys.argv = save_argv
1723 sys.argv = save_argv
1715 if restore_main:
1724 if restore_main:
@@ -1719,6 +1728,7 b' Currently the magic system has the following functions:\\n"""'
1719 # added. Otherwise it will trap references to objects
1728 # added. Otherwise it will trap references to objects
1720 # contained therein.
1729 # contained therein.
1721 del sys.modules[main_mod_name]
1730 del sys.modules[main_mod_name]
1731
1722 self.shell.reloadhist()
1732 self.shell.reloadhist()
1723
1733
1724 return stats
1734 return stats
@@ -1800,7 +1810,28 b' Currently the magic system has the following functions:\\n"""'
1800 import timeit
1810 import timeit
1801 import math
1811 import math
1802
1812
1803 units = [u"s", u"ms", u"\xb5s", u"ns"]
1813 # XXX: Unfortunately the unicode 'micro' symbol can cause problems in
1814 # certain terminals. Until we figure out a robust way of
1815 # auto-detecting if the terminal can deal with it, use plain 'us' for
1816 # microseconds. I am really NOT happy about disabling the proper
1817 # 'micro' prefix, but crashing is worse... If anyone knows what the
1818 # right solution for this is, I'm all ears...
1819 #
1820 # Note: using
1821 #
1822 # s = u'\xb5'
1823 # s.encode(sys.getdefaultencoding())
1824 #
1825 # is not sufficient, as I've seen terminals where that fails but
1826 # print s
1827 #
1828 # succeeds
1829 #
1830 # See bug: https://bugs.launchpad.net/ipython/+bug/348466
1831
1832 #units = [u"s", u"ms",u'\xb5',"ns"]
1833 units = [u"s", u"ms",u'us',"ns"]
1834
1804 scaling = [1, 1e3, 1e6, 1e9]
1835 scaling = [1, 1e3, 1e6, 1e9]
1805
1836
1806 opts, stmt = self.parse_options(parameter_s,'n:r:tcp:',
1837 opts, stmt = self.parse_options(parameter_s,'n:r:tcp:',
@@ -1839,9 +1870,9 b' Currently the magic system has the following functions:\\n"""'
1839 # determine number so that 0.2 <= total time < 2.0
1870 # determine number so that 0.2 <= total time < 2.0
1840 number = 1
1871 number = 1
1841 for i in range(1, 10):
1872 for i in range(1, 10):
1842 number *= 10
1843 if timer.timeit(number) >= 0.2:
1873 if timer.timeit(number) >= 0.2:
1844 break
1874 break
1875 number *= 10
1845
1876
1846 best = min(timer.repeat(repeat, number)) / number
1877 best = min(timer.repeat(repeat, number)) / number
1847
1878
@@ -126,9 +126,13 b' prompt_specials_color = {'
126 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
126 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
127 # can get numbers displayed in whatever color they want.
127 # can get numbers displayed in whatever color they want.
128 r'\N': '${self.cache.prompt_count}',
128 r'\N': '${self.cache.prompt_count}',
129
129 # Prompt/history count, with the actual digits replaced by dots. Used
130 # Prompt/history count, with the actual digits replaced by dots. Used
130 # mainly in continuation prompts (prompt_in2)
131 # mainly in continuation prompts (prompt_in2)
131 r'\D': '${"."*len(str(self.cache.prompt_count))}',
132 #r'\D': '${"."*len(str(self.cache.prompt_count))}',
133 # More robust form of the above expression, that uses __builtins__
134 r'\D': '${"."*__builtins__.len(__builtins__.str(self.cache.prompt_count))}',
135
132 # Current working directory
136 # Current working directory
133 r'\w': '${os.getcwd()}',
137 r'\w': '${os.getcwd()}',
134 # Current time
138 # Current time
@@ -20,10 +20,10 b" name = 'ipython'"
20 # because bdist_rpm does not accept dashes (an RPM) convention, and
20 # because bdist_rpm does not accept dashes (an RPM) convention, and
21 # bdist_deb does not accept underscores (a Debian convention).
21 # bdist_deb does not accept underscores (a Debian convention).
22
22
23 development = False # change this to False to do a release
23 development = True # change this to False to do a release
24 version_base = '0.9.1'
24 version_base = '0.10'
25 branch = 'ipython'
25 branch = 'ipython'
26 revision = '1143'
26 revision = '1163'
27
27
28 if development:
28 if development:
29 if branch == 'ipython':
29 if branch == 'ipython':
@@ -292,17 +292,19 b' if HAS_CTYPES:'
292 """raises the exception, performs cleanup if needed"""
292 """raises the exception, performs cleanup if needed"""
293 if not inspect.isclass(exctype):
293 if not inspect.isclass(exctype):
294 raise TypeError("Only types can be raised (not instances)")
294 raise TypeError("Only types can be raised (not instances)")
295 res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid,
295 # Explicit cast to c_long is necessary for 64-bit support:
296 # See https://bugs.launchpad.net/ipython/+bug/237073
297 res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid),
296 ctypes.py_object(exctype))
298 ctypes.py_object(exctype))
297 if res == 0:
299 if res == 0:
298 raise ValueError("invalid thread id")
300 raise ValueError("invalid thread id")
299 elif res != 1:
301 elif res != 1:
300 # """if it returns a number greater than one, you're in trouble,
302 # If it returns a number greater than one, you're in trouble,
301 # and you should call it again with exc=NULL to revert the effect"""
303 # and you should call it again with exc=NULL to revert the effect
302 ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, 0)
304 ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, 0)
303 raise SystemError("PyThreadState_SetAsyncExc failed")
305 raise SystemError("PyThreadState_SetAsyncExc failed")
304
306
305 def sigint_handler (signum,stack_frame):
307 def sigint_handler(signum,stack_frame):
306 """Sigint handler for threaded apps.
308 """Sigint handler for threaded apps.
307
309
308 This is a horrible hack to pass information about SIGINT _without_
310 This is a horrible hack to pass information about SIGINT _without_
@@ -321,7 +323,7 b' if HAS_CTYPES:'
321 Term.cout.flush()
323 Term.cout.flush()
322
324
323 else:
325 else:
324 def sigint_handler (signum,stack_frame):
326 def sigint_handler(signum,stack_frame):
325 """Sigint handler for threaded apps.
327 """Sigint handler for threaded apps.
326
328
327 This is a horrible hack to pass information about SIGINT _without_
329 This is a horrible hack to pass information about SIGINT _without_
@@ -99,16 +99,14 b' def main():'
99 #import readline
99 #import readline
100 #readline.parse_and_bind('set completion-query-items 1000')
100 #readline.parse_and_bind('set completion-query-items 1000')
101 #readline.parse_and_bind('set page-completions no')
101 #readline.parse_and_bind('set page-completions no')
102
102
103
103
104
105
106 # some config helper functions you can use
104 # some config helper functions you can use
107 def import_all(modules):
105 def import_all(modules):
108 """ Usage: import_all("os sys") """
106 """ Usage: import_all("os sys") """
109 for m in modules.split():
107 for m in modules.split():
110 ip.ex("from %s import *" % m)
108 ip.ex("from %s import *" % m)
111
109
112 def execf(fname):
110 def execf(fname):
113 """ Execute a file in user namespace """
111 """ Execute a file in user namespace """
114 ip.ex('execfile("%s")' % os.path.expanduser(fname))
112 ip.ex('execfile("%s")' % os.path.expanduser(fname))
@@ -248,7 +248,7 b' class BackgroundJobManager:'
248 self._update_status()
248 self._update_status()
249 new_comp = self._group_report(self._comp_report,'Completed')
249 new_comp = self._group_report(self._comp_report,'Completed')
250 new_dead = self._group_report(self._dead_report,
250 new_dead = self._group_report(self._dead_report,
251 'Dead, call job.traceback() for details')
251 'Dead, call jobs.traceback() for details')
252 self._comp_report[:] = []
252 self._comp_report[:] = []
253 self._dead_report[:] = []
253 self._dead_report[:] = []
254 return new_comp or new_dead
254 return new_comp or new_dead
@@ -340,7 +340,7 b' class BackgroundJobBase(threading.Thread):'
340 stat_created = 'Created'; stat_created_c = 0
340 stat_created = 'Created'; stat_created_c = 0
341 stat_running = 'Running'; stat_running_c = 1
341 stat_running = 'Running'; stat_running_c = 1
342 stat_completed = 'Completed'; stat_completed_c = 2
342 stat_completed = 'Completed'; stat_completed_c = 2
343 stat_dead = 'Dead (Exception), call job.traceback() for details'
343 stat_dead = 'Dead (Exception), call jobs.traceback() for details'
344 stat_dead_c = -1
344 stat_dead_c = -1
345
345
346 def __init__(self):
346 def __init__(self):
@@ -391,7 +391,7 b' class BackgroundJobBase(threading.Thread):'
391 self.status = BackgroundJobBase.stat_dead
391 self.status = BackgroundJobBase.stat_dead
392 self.stat_code = BackgroundJobBase.stat_dead_c
392 self.stat_code = BackgroundJobBase.stat_dead_c
393 self.finished = None
393 self.finished = None
394 self.result = ('<BackgroundJob died, call job.traceback() for details>')
394 self.result = ('<BackgroundJob died, call jobs.traceback() for details>')
395 self._tb = self._make_tb()
395 self._tb = self._make_tb()
396 else:
396 else:
397 self.status = BackgroundJobBase.stat_completed
397 self.status = BackgroundJobBase.stat_completed
@@ -14,12 +14,13 b' __docformat__ = "restructuredtext en"'
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17 from IPython.external import guid
18
17
18 from IPython.external import guid
19
19
20 from zope.interface import Interface, Attribute, implements, classProvides
20 from zope.interface import Interface, Attribute, implements, classProvides
21 from twisted.python.failure import Failure
21 from twisted.python.failure import Failure
22 from IPython.frontend.frontendbase import FrontEndBase, IFrontEnd, IFrontEndFactory
22 from IPython.frontend.frontendbase import (
23 FrontEndBase, IFrontEnd, IFrontEndFactory)
23 from IPython.kernel.core.history import FrontEndHistory
24 from IPython.kernel.core.history import FrontEndHistory
24 from IPython.kernel.engineservice import IEngineCore
25 from IPython.kernel.engineservice import IEngineCore
25
26
@@ -15,30 +15,38 b' __docformat__ = "restructuredtext en"'
15 # Imports
15 # Imports
16 #---------------------------------------------------------------------------
16 #---------------------------------------------------------------------------
17
17
18 # Tell nose to skip this module
19 __test__ = {}
20
21 from twisted.trial import unittest
22 from twisted.internet.defer import succeed
23
24 from IPython.kernel.core.interpreter import Interpreter
25 import IPython.kernel.engineservice as es
26
18 try:
27 try:
19 from IPython.kernel.core.interpreter import Interpreter
28 from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController
20 import IPython.kernel.engineservice as es
21 from IPython.testing.util import DeferredTestCase
22 from twisted.internet.defer import succeed
23 from IPython.frontend.cocoa.cocoa_frontend import IPythonCocoaController
24 from Foundation import NSMakeRect
29 from Foundation import NSMakeRect
25 from AppKit import NSTextView, NSScrollView
30 from AppKit import NSTextView, NSScrollView
26 except ImportError:
31 except ImportError:
27 import nose
32 # This tells twisted.trial to skip this module if PyObjC is not found
28 raise nose.SkipTest("This test requires zope.interface, Twisted, Foolscap and PyObjC")
33 skip = True
29
34
30 class TestIPythonCocoaControler(DeferredTestCase):
35 #---------------------------------------------------------------------------
36 # Tests
37 #---------------------------------------------------------------------------
38 class TestIPythonCocoaControler(unittest.TestCase):
31 """Tests for IPythonCocoaController"""
39 """Tests for IPythonCocoaController"""
32
40
33 def setUp(self):
41 def setUp(self):
34 self.controller = IPythonCocoaController.alloc().init()
42 self.controller = IPythonCocoaController.alloc().init()
35 self.engine = es.EngineService()
43 self.engine = es.EngineService()
36 self.engine.startService()
44 self.engine.startService()
37
45
38 def tearDown(self):
46 def tearDown(self):
39 self.controller = None
47 self.controller = None
40 self.engine.stopService()
48 self.engine.stopService()
41
49
42 def testControllerExecutesCode(self):
50 def testControllerExecutesCode(self):
43 code ="""5+5"""
51 code ="""5+5"""
44 expected = Interpreter().execute(code)
52 expected = Interpreter().execute(code)
@@ -47,48 +55,46 b' class TestIPythonCocoaControler(DeferredTestCase):'
47 del result['number']
55 del result['number']
48 del result['id']
56 del result['id']
49 return result
57 return result
50 self.assertDeferredEquals(
58 d = self.controller.execute(code)
51 self.controller.execute(code).addCallback(removeNumberAndID),
59 d.addCallback(removeNumberAndID)
52 expected)
60 d.addCallback(lambda r: self.assertEquals(r, expected))
53
61
54 def testControllerMirrorsUserNSWithValuesAsStrings(self):
62 def testControllerMirrorsUserNSWithValuesAsStrings(self):
55 code = """userns1=1;userns2=2"""
63 code = """userns1=1;userns2=2"""
56 def testControllerUserNS(result):
64 def testControllerUserNS(result):
57 self.assertEquals(self.controller.userNS['userns1'], 1)
65 self.assertEquals(self.controller.userNS['userns1'], 1)
58 self.assertEquals(self.controller.userNS['userns2'], 2)
66 self.assertEquals(self.controller.userNS['userns2'], 2)
59
60 self.controller.execute(code).addCallback(testControllerUserNS)
67 self.controller.execute(code).addCallback(testControllerUserNS)
61
68
62
63 def testControllerInstantiatesIEngine(self):
69 def testControllerInstantiatesIEngine(self):
64 self.assert_(es.IEngineBase.providedBy(self.controller.engine))
70 self.assert_(es.IEngineBase.providedBy(self.controller.engine))
65
71
66 def testControllerCompletesToken(self):
72 def testControllerCompletesToken(self):
67 code = """longNameVariable=10"""
73 code = """longNameVariable=10"""
68 def testCompletes(result):
74 def testCompletes(result):
69 self.assert_("longNameVariable" in result)
75 self.assert_("longNameVariable" in result)
70
76
71 def testCompleteToken(result):
77 def testCompleteToken(result):
72 self.controller.complete("longNa").addCallback(testCompletes)
78 self.controller.complete("longNa").addCallback(testCompletes)
73
79
74 self.controller.execute(code).addCallback(testCompletes)
80 self.controller.execute(code).addCallback(testCompletes)
75
81
76
82
77 def testCurrentIndent(self):
83 def testCurrentIndent(self):
78 """test that current_indent_string returns current indent or None.
84 """test that current_indent_string returns current indent or None.
79 Uses _indent_for_block for direct unit testing.
85 Uses _indent_for_block for direct unit testing.
80 """
86 """
81
87
82 self.controller.tabUsesSpaces = True
88 self.controller.tabUsesSpaces = True
83 self.assert_(self.controller._indent_for_block("""a=3""") == None)
89 self.assert_(self.controller._indent_for_block("""a=3""") == None)
84 self.assert_(self.controller._indent_for_block("") == None)
90 self.assert_(self.controller._indent_for_block("") == None)
85 block = """def test():\n a=3"""
91 block = """def test():\n a=3"""
86 self.assert_(self.controller._indent_for_block(block) == \
92 self.assert_(self.controller._indent_for_block(block) == \
87 ' ' * self.controller.tabSpaces)
93 ' ' * self.controller.tabSpaces)
88
94
89 block = """if(True):\n%sif(False):\n%spass""" % \
95 block = """if(True):\n%sif(False):\n%spass""" % \
90 (' '*self.controller.tabSpaces,
96 (' '*self.controller.tabSpaces,
91 2*' '*self.controller.tabSpaces)
97 2*' '*self.controller.tabSpaces)
92 self.assert_(self.controller._indent_for_block(block) == \
98 self.assert_(self.controller._indent_for_block(block) == \
93 2*(' '*self.controller.tabSpaces))
99 2*(' '*self.controller.tabSpaces))
94
100
@@ -18,10 +18,8 b' __docformat__ = "restructuredtext en"'
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19 import re
19 import re
20
20
21 import IPython
22 import sys
21 import sys
23 import codeop
22 import codeop
24 import traceback
25
23
26 from frontendbase import FrontEndBase
24 from frontendbase import FrontEndBase
27 from IPython.kernel.core.interpreter import Interpreter
25 from IPython.kernel.core.interpreter import Interpreter
@@ -58,6 +56,9 b' class LineFrontEndBase(FrontEndBase):'
58 # programatic control of the frontend.
56 # programatic control of the frontend.
59 last_result = dict(number=0)
57 last_result = dict(number=0)
60
58
59 # The last prompt displayed. Useful for continuation prompts.
60 last_prompt = ''
61
61 # The input buffer being edited
62 # The input buffer being edited
62 input_buffer = ''
63 input_buffer = ''
63
64
@@ -151,8 +152,12 b' class LineFrontEndBase(FrontEndBase):'
151 self.capture_output()
152 self.capture_output()
152 try:
153 try:
153 # Add line returns here, to make sure that the statement is
154 # Add line returns here, to make sure that the statement is
154 # complete.
155 # complete (except if '\' was used).
155 is_complete = codeop.compile_command(string.rstrip() + '\n\n',
156 # This should probably be done in a different place (like
157 # maybe 'prefilter_input' method? For now, this works.
158 clean_string = string.rstrip('\n')
159 if not clean_string.endswith('\\'): clean_string +='\n\n'
160 is_complete = codeop.compile_command(clean_string,
156 "<string>", "exec")
161 "<string>", "exec")
157 self.release_output()
162 self.release_output()
158 except Exception, e:
163 except Exception, e:
@@ -183,16 +188,6 b' class LineFrontEndBase(FrontEndBase):'
183 # Create a false result, in case there is an exception
188 # Create a false result, in case there is an exception
184 self.last_result = dict(number=self.prompt_number)
189 self.last_result = dict(number=self.prompt_number)
185
190
186 ## try:
187 ## self.history.input_cache[-1] = raw_string.rstrip()
188 ## result = self.shell.execute(python_string)
189 ## self.last_result = result
190 ## self.render_result(result)
191 ## except:
192 ## self.show_traceback()
193 ## finally:
194 ## self.after_execute()
195
196 try:
191 try:
197 try:
192 try:
198 self.history.input_cache[-1] = raw_string.rstrip()
193 self.history.input_cache[-1] = raw_string.rstrip()
@@ -272,15 +267,15 b' class LineFrontEndBase(FrontEndBase):'
272 symbols_per_line = max(1, chars_per_line/max_len)
267 symbols_per_line = max(1, chars_per_line/max_len)
273
268
274 pos = 1
269 pos = 1
275 buf = []
270 completion_string = []
276 for symbol in possibilities:
271 for symbol in possibilities:
277 if pos < symbols_per_line:
272 if pos < symbols_per_line:
278 buf.append(symbol.ljust(max_len))
273 completion_string.append(symbol.ljust(max_len))
279 pos += 1
274 pos += 1
280 else:
275 else:
281 buf.append(symbol.rstrip() + '\n')
276 completion_string.append(symbol.rstrip() + '\n')
282 pos = 1
277 pos = 1
283 self.write(''.join(buf))
278 self.write(''.join(completion_string))
284 self.new_prompt(self.input_prompt_template.substitute(
279 self.new_prompt(self.input_prompt_template.substitute(
285 number=self.last_result['number'] + 1))
280 number=self.last_result['number'] + 1))
286 self.input_buffer = new_line
281 self.input_buffer = new_line
@@ -297,26 +292,70 b' class LineFrontEndBase(FrontEndBase):'
297 self.write(prompt)
292 self.write(prompt)
298
293
299
294
295 def continuation_prompt(self):
296 """Returns the current continuation prompt.
297 """
298 return ("."*(len(self.last_prompt)-2) + ': ')
299
300
301 def execute_command(self, command, hidden=False):
302 """ Execute a command, not only in the model, but also in the
303 view, if any.
304 """
305 return self.shell.execute(command)
306
300 #--------------------------------------------------------------------------
307 #--------------------------------------------------------------------------
301 # Private API
308 # Private API
302 #--------------------------------------------------------------------------
309 #--------------------------------------------------------------------------
303
310
304 def _on_enter(self):
311 def _on_enter(self, new_line_pos=0):
305 """ Called when the return key is pressed in a line editing
312 """ Called when the return key is pressed in a line editing
306 buffer.
313 buffer.
314
315 Parameters
316 ----------
317 new_line_pos : integer, optional
318 Position of the new line to add, starting from the
319 end (0 adds a new line after the last line, -1 before
320 the last line...)
321
322 Returns
323 -------
324 True if execution is triggered
307 """
325 """
308 current_buffer = self.input_buffer
326 current_buffer = self.input_buffer
309 cleaned_buffer = self.prefilter_input(current_buffer)
327 # XXX: This string replace is ugly, but there should be no way it
328 # fails.
329 prompt_less_buffer = re.sub('^' + self.continuation_prompt(),
330 '', current_buffer).replace('\n' + self.continuation_prompt(),
331 '\n')
332 cleaned_buffer = self.prefilter_input(prompt_less_buffer)
310 if self.is_complete(cleaned_buffer):
333 if self.is_complete(cleaned_buffer):
311 self.execute(cleaned_buffer, raw_string=current_buffer)
334 self.execute(cleaned_buffer, raw_string=current_buffer)
335 return True
312 else:
336 else:
313 self.input_buffer += self._get_indent_string(
337 # Start a new line.
314 current_buffer[:-1])
338 new_line_pos = -new_line_pos
315 if len(current_buffer.split('\n')) == 2:
339 lines = current_buffer.split('\n')[:-1]
316 self.input_buffer += '\t\t'
340 prompt_less_lines = prompt_less_buffer.split('\n')
317 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
341 # Create the new line, with the continuation prompt, and the
318 self.input_buffer += '\t'
342 # same amount of indent than the line above it.
319
343 new_line = self.continuation_prompt() + \
344 self._get_indent_string('\n'.join(
345 prompt_less_lines[:new_line_pos-1]))
346 if len(lines) == 1:
347 # We are starting a first continuation line. Indent it.
348 new_line += '\t'
349 elif current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
350 # The last line ends with ":", autoindent the new line.
351 new_line += '\t'
352
353 if new_line_pos == 0:
354 lines.append(new_line)
355 else:
356 lines.insert(new_line_pos, new_line)
357 self.input_buffer = '\n'.join(lines)
358
320
359
321 def _get_indent_string(self, string):
360 def _get_indent_string(self, string):
322 """ Return the string of whitespace that prefixes a line. Used to
361 """ Return the string of whitespace that prefixes a line. Used to
@@ -22,9 +22,10 b' __docformat__ = "restructuredtext en"'
22 # Imports
22 # Imports
23 #-------------------------------------------------------------------------------
23 #-------------------------------------------------------------------------------
24 import sys
24 import sys
25
25 import pydoc
26 from linefrontendbase import LineFrontEndBase, common_prefix
26 import os
27 from frontendbase import FrontEndBase
27 import re
28 import __builtin__
28
29
29 from IPython.ipmaker import make_IPython
30 from IPython.ipmaker import make_IPython
30 from IPython.ipapi import IPApi
31 from IPython.ipapi import IPApi
@@ -33,9 +34,8 b' from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap'
33 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
34 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
34
35
35 from IPython.genutils import Term
36 from IPython.genutils import Term
36 import pydoc
37
37 import os
38 from linefrontendbase import LineFrontEndBase, common_prefix
38 import sys
39
39
40
40
41 def mk_system_call(system_call_function, command):
41 def mk_system_call(system_call_function, command):
@@ -45,6 +45,8 b' def mk_system_call(system_call_function, command):'
45 """
45 """
46 def my_system_call(args):
46 def my_system_call(args):
47 system_call_function("%s %s" % (command, args))
47 system_call_function("%s %s" % (command, args))
48
49 my_system_call.__doc__ = "Calls %s" % command
48 return my_system_call
50 return my_system_call
49
51
50 #-------------------------------------------------------------------------------
52 #-------------------------------------------------------------------------------
@@ -62,13 +64,25 b' class PrefilterFrontEnd(LineFrontEndBase):'
62
64
63 debug = False
65 debug = False
64
66
65 def __init__(self, ipython0=None, *args, **kwargs):
67 def __init__(self, ipython0=None, argv=None, *args, **kwargs):
66 """ Parameters:
68 """ Parameters:
67 -----------
69 -----------
68
70
69 ipython0: an optional ipython0 instance to use for command
71 ipython0: an optional ipython0 instance to use for command
70 prefiltering and completion.
72 prefiltering and completion.
73
74 argv : list, optional
75 Used as the instance's argv value. If not given, [] is used.
71 """
76 """
77 if argv is None:
78 argv = []
79 # This is a hack to avoid the IPython exception hook to trigger
80 # on exceptions (https://bugs.launchpad.net/bugs/337105)
81 # XXX: This is horrible: module-leve monkey patching -> side
82 # effects.
83 from IPython import iplib
84 iplib.InteractiveShell.isthreaded = True
85
72 LineFrontEndBase.__init__(self, *args, **kwargs)
86 LineFrontEndBase.__init__(self, *args, **kwargs)
73 self.shell.output_trap = RedirectorOutputTrap(
87 self.shell.output_trap = RedirectorOutputTrap(
74 out_callback=self.write,
88 out_callback=self.write,
@@ -83,10 +97,16 b' class PrefilterFrontEnd(LineFrontEndBase):'
83 if ipython0 is None:
97 if ipython0 is None:
84 # Instanciate an IPython0 interpreter to be able to use the
98 # Instanciate an IPython0 interpreter to be able to use the
85 # prefiltering.
99 # prefiltering.
100 # Suppress all key input, to avoid waiting
101 def my_rawinput(x=None):
102 return '\n'
103 old_rawinput = __builtin__.raw_input
104 __builtin__.raw_input = my_rawinput
86 # XXX: argv=[] is a bit bold.
105 # XXX: argv=[] is a bit bold.
87 ipython0 = make_IPython(argv=[],
106 ipython0 = make_IPython(argv=argv,
88 user_ns=self.shell.user_ns,
107 user_ns=self.shell.user_ns,
89 user_global_ns=self.shell.user_global_ns)
108 user_global_ns=self.shell.user_global_ns)
109 __builtin__.raw_input = old_rawinput
90 self.ipython0 = ipython0
110 self.ipython0 = ipython0
91 # Set the pager:
111 # Set the pager:
92 self.ipython0.set_hook('show_in_pager',
112 self.ipython0.set_hook('show_in_pager',
@@ -98,16 +118,17 b' class PrefilterFrontEnd(LineFrontEndBase):'
98 self._ip.system = self.system_call
118 self._ip.system = self.system_call
99 # XXX: Muck around with magics so that they work better
119 # XXX: Muck around with magics so that they work better
100 # in our environment
120 # in our environment
101 self.ipython0.magic_ls = mk_system_call(self.system_call,
121 if not sys.platform.startswith('win'):
102 'ls -CF')
122 self.ipython0.magic_ls = mk_system_call(self.system_call,
123 'ls -CF')
103 # And now clean up the mess created by ipython0
124 # And now clean up the mess created by ipython0
104 self.release_output()
125 self.release_output()
105
126
106
127
107 if not 'banner' in kwargs and self.banner is None:
128 if not 'banner' in kwargs and self.banner is None:
108 self.banner = self.ipython0.BANNER + """
129 self.banner = self.ipython0.BANNER
109 This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code."""
110
130
131 # FIXME: __init__ and start should be two different steps
111 self.start()
132 self.start()
112
133
113 #--------------------------------------------------------------------------
134 #--------------------------------------------------------------------------
@@ -117,7 +138,10 b' This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code."""'
117 def show_traceback(self):
138 def show_traceback(self):
118 """ Use ipython0 to capture the last traceback and display it.
139 """ Use ipython0 to capture the last traceback and display it.
119 """
140 """
120 self.capture_output()
141 # Don't do the capture; the except_hook has already done some
142 # modifications to the IO streams, if we store them, we'll be
143 # storing the wrong ones.
144 #self.capture_output()
121 self.ipython0.showtraceback(tb_offset=-1)
145 self.ipython0.showtraceback(tb_offset=-1)
122 self.release_output()
146 self.release_output()
123
147
@@ -171,7 +195,7 b' This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code."""'
171 def complete(self, line):
195 def complete(self, line):
172 # FIXME: This should be factored out in the linefrontendbase
196 # FIXME: This should be factored out in the linefrontendbase
173 # method.
197 # method.
174 word = line.split('\n')[-1].split(' ')[-1]
198 word = self._get_completion_text(line)
175 completions = self.ipython0.complete(word)
199 completions = self.ipython0.complete(word)
176 # FIXME: The proper sort should be done in the complete method.
200 # FIXME: The proper sort should be done in the complete method.
177 key = lambda x: x.replace('_', '')
201 key = lambda x: x.replace('_', '')
@@ -244,3 +268,18 b' This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code."""'
244 """
268 """
245 self.ipython0.atexit_operations()
269 self.ipython0.atexit_operations()
246
270
271
272 def _get_completion_text(self, line):
273 """ Returns the text to be completed by breaking the line at specified
274 delimiters.
275 """
276 # Break at: spaces, '=', all parentheses (except if balanced).
277 # FIXME2: In the future, we need to make the implementation similar to
278 # that in the 'pyreadline' module (modes/basemode.py) where we break at
279 # each delimiter and try to complete the residual line, until we get a
280 # successful list of completions.
281 expression = '\s|=|,|:|\((?!.*\))|\[(?!.*\])|\{(?!.*\})'
282 complete_sep = re.compile(expression)
283 text = complete_sep.split(line)[-1]
284 return text
285
1 NO CONTENT: file renamed from IPython/frontend/_process/__init__.py to IPython/frontend/process/__init__.py
NO CONTENT: file renamed from IPython/frontend/_process/__init__.py to IPython/frontend/process/__init__.py
@@ -151,7 +151,12 b' else:'
151 self._thread = ht
151 self._thread = ht
152 self.pid = pid
152 self.pid = pid
153
153
154 winprocess.AssignProcessToJobObject(self._job, hp)
154 # XXX: A try/except to fix UAC-related problems under
155 # Windows Vista, when reparenting jobs.
156 try:
157 winprocess.AssignProcessToJobObject(self._job, hp)
158 except WindowsError:
159 pass
155 winprocess.ResumeThread(ht)
160 winprocess.ResumeThread(ht)
156
161
157 if p2cread is not None:
162 if p2cread is not None:
1 NO CONTENT: file renamed from IPython/frontend/_process/pipedprocess.py to IPython/frontend/process/pipedprocess.py
NO CONTENT: file renamed from IPython/frontend/_process/pipedprocess.py to IPython/frontend/process/pipedprocess.py
1 NO CONTENT: file renamed from IPython/frontend/_process/winprocess.py to IPython/frontend/process/winprocess.py
NO CONTENT: file renamed from IPython/frontend/_process/winprocess.py to IPython/frontend/process/winprocess.py
@@ -1,6 +1,6 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """This file contains unittests for the frontendbase module."""
3 """This file contains unittests for the asyncfrontendbase module."""
4
4
5 __docformat__ = "restructuredtext en"
5 __docformat__ = "restructuredtext en"
6
6
@@ -15,17 +15,15 b' __docformat__ = "restructuredtext en"'
15 # Imports
15 # Imports
16 #---------------------------------------------------------------------------
16 #---------------------------------------------------------------------------
17
17
18 import unittest
18 # Tell nose to skip this module
19 __test__ = {}
19
20
20 try:
21 from twisted.trial import unittest
21 from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase
22 from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase
22 from IPython.frontend import frontendbase
23 from IPython.frontend import frontendbase
23 from IPython.kernel.engineservice import EngineService
24 from IPython.kernel.engineservice import EngineService
24 except ImportError:
25 from IPython.testing.parametric import Parametric, parametric
25 import nose
26 raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap")
27
26
28 from IPython.testing.decorators import skip
29
27
30 class FrontEndCallbackChecker(AsyncFrontEndBase):
28 class FrontEndCallbackChecker(AsyncFrontEndBase):
31 """FrontEndBase subclass for checking callbacks"""
29 """FrontEndBase subclass for checking callbacks"""
@@ -44,14 +42,11 b' class FrontEndCallbackChecker(AsyncFrontEndBase):'
44 self.renderResultCalled = True
42 self.renderResultCalled = True
45 return result
43 return result
46
44
47
48 def render_error(self, failure):
45 def render_error(self, failure):
49 self.renderErrorCalled = True
46 self.renderErrorCalled = True
50 return failure
47 return failure
51
52
48
53
49
54
55 class TestAsyncFrontendBase(unittest.TestCase):
50 class TestAsyncFrontendBase(unittest.TestCase):
56 def setUp(self):
51 def setUp(self):
57 """Setup the EngineService and FrontEndBase"""
52 """Setup the EngineService and FrontEndBase"""
@@ -59,97 +54,56 b' class TestAsyncFrontendBase(unittest.TestCase):'
59 self.fb = FrontEndCallbackChecker(engine=EngineService())
54 self.fb = FrontEndCallbackChecker(engine=EngineService())
60
55
61 def test_implements_IFrontEnd(self):
56 def test_implements_IFrontEnd(self):
62 assert(frontendbase.IFrontEnd.implementedBy(
57 self.assert_(frontendbase.IFrontEnd.implementedBy(
63 AsyncFrontEndBase))
58 AsyncFrontEndBase))
64
59
65 def test_is_complete_returns_False_for_incomplete_block(self):
60 def test_is_complete_returns_False_for_incomplete_block(self):
66 """"""
67
68 block = """def test(a):"""
61 block = """def test(a):"""
69
62 self.assert_(self.fb.is_complete(block) == False)
70 assert(self.fb.is_complete(block) == False)
71
63
72 def test_is_complete_returns_True_for_complete_block(self):
64 def test_is_complete_returns_True_for_complete_block(self):
73 """"""
74
75 block = """def test(a): pass"""
65 block = """def test(a): pass"""
76
66 self.assert_(self.fb.is_complete(block))
77 assert(self.fb.is_complete(block))
78
79 block = """a=3"""
67 block = """a=3"""
80
68 self.assert_(self.fb.is_complete(block))
81 assert(self.fb.is_complete(block))
82
69
83 def test_blockID_added_to_result(self):
70 def test_blockID_added_to_result(self):
84 block = """3+3"""
71 block = """3+3"""
85
86 d = self.fb.execute(block, blockID='TEST_ID')
72 d = self.fb.execute(block, blockID='TEST_ID')
87
73 d.addCallback(lambda r: self.assert_(r['blockID']=='TEST_ID'))
88 d.addCallback(self.checkBlockID, expected='TEST_ID')
74 return d
89
75
90 def test_blockID_added_to_failure(self):
76 def test_blockID_added_to_failure(self):
91 block = "raise Exception()"
77 block = "raise Exception()"
92
93 d = self.fb.execute(block,blockID='TEST_ID')
78 d = self.fb.execute(block,blockID='TEST_ID')
94 d.addErrback(self.checkFailureID, expected='TEST_ID')
79 d.addErrback(lambda f: self.assert_(f.blockID=='TEST_ID'))
95
80 return d
96 def checkBlockID(self, result, expected=""):
97 assert(result['blockID'] == expected)
98
99
100 def checkFailureID(self, failure, expected=""):
101 assert(failure.blockID == expected)
102
103
81
104 def test_callbacks_added_to_execute(self):
82 def test_callbacks_added_to_execute(self):
105 """test that
106 update_cell_prompt
107 render_result
108
109 are added to execute request
110 """
111
112 d = self.fb.execute("10+10")
83 d = self.fb.execute("10+10")
113 d.addCallback(self.checkCallbacks)
84 d.addCallback(lambda r: self.assert_(self.fb.updateCalled and self.fb.renderResultCalled))
85 return d
114
86
115 def checkCallbacks(self, result):
116 assert(self.fb.updateCalled)
117 assert(self.fb.renderResultCalled)
118
119 @skip("This test fails and lead to an unhandled error in a Deferred.")
120 def test_error_callback_added_to_execute(self):
87 def test_error_callback_added_to_execute(self):
121 """test that render_error called on execution error"""
88 """Test that render_error called on execution error."""
122
89
123 d = self.fb.execute("raise Exception()")
90 d = self.fb.execute("raise Exception()")
124 d.addCallback(self.checkRenderError)
91 d.addErrback(lambda f: self.assert_(self.fb.renderErrorCalled))
125
92 return d
126 def checkRenderError(self, result):
127 assert(self.fb.renderErrorCalled)
128
93
129 def test_history_returns_expected_block(self):
94 def test_history_returns_expected_block(self):
130 """Make sure history browsing doesn't fail"""
95 """Make sure history browsing doesn't fail."""
131
96
132 blocks = ["a=1","a=2","a=3"]
97 blocks = ["a=1","a=2","a=3"]
133 for b in blocks:
98 d = self.fb.execute(blocks[0])
134 d = self.fb.execute(b)
99 d.addCallback(lambda _: self.fb.execute(blocks[1]))
135
100 d.addCallback(lambda _: self.fb.execute(blocks[2]))
136 # d is now the deferred for the last executed block
101 d.addCallback(lambda _: self.assert_(self.fb.get_history_previous("")==blocks[-2]))
137 d.addCallback(self.historyTests, blocks)
102 d.addCallback(lambda _: self.assert_(self.fb.get_history_previous("")==blocks[-3]))
138
103 d.addCallback(lambda _: self.assert_(self.fb.get_history_next()==blocks[-2]))
139
104 return d
140 def historyTests(self, result, blocks):
105
141 """historyTests"""
106 def test_history_returns_none_at_startup(self):
142
107 self.assert_(self.fb.get_history_previous("")==None)
143 assert(len(blocks) >= 3)
108 self.assert_(self.fb.get_history_next()==None)
144 assert(self.fb.get_history_previous("") == blocks[-2])
109
145 assert(self.fb.get_history_previous("") == blocks[-3])
146 assert(self.fb.get_history_next() == blocks[-2])
147
148
149 def test_history_returns_none_at_startup(self):
150 """test_history_returns_none_at_startup"""
151
152 assert(self.fb.get_history_previous("")==None)
153 assert(self.fb.get_history_next()==None)
154
155
@@ -12,12 +12,32 b' __docformat__ = "restructuredtext en"'
12 # in the file COPYING, distributed as part of this software.
12 # in the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14
14
15 from copy import copy, deepcopy
15 from cStringIO import StringIO
16 from cStringIO import StringIO
16 import string
17 import string
17
18
18 from IPython.ipapi import get as get_ipython0
19 from nose.tools import assert_equal
20
19 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
21 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
20 from copy import deepcopy
22 from IPython.ipapi import get as get_ipython0
23 from IPython.testing.plugin.ipdoctest import default_argv
24
25
26 def safe_deepcopy(d):
27 """ Deep copy every key of the given dict, when possible. Elsewhere
28 do a copy.
29 """
30 copied_d = dict()
31 for key, value in d.iteritems():
32 try:
33 copied_d[key] = deepcopy(value)
34 except:
35 try:
36 copied_d[key] = copy(value)
37 except:
38 copied_d[key] = value
39 return copied_d
40
21
41
22 class TestPrefilterFrontEnd(PrefilterFrontEnd):
42 class TestPrefilterFrontEnd(PrefilterFrontEnd):
23
43
@@ -26,16 +46,8 b' class TestPrefilterFrontEnd(PrefilterFrontEnd):'
26 banner = ''
46 banner = ''
27
47
28 def __init__(self):
48 def __init__(self):
29 ipython0 = get_ipython0().IP
30 self.out = StringIO()
49 self.out = StringIO()
31 PrefilterFrontEnd.__init__(self, ipython0=ipython0)
50 PrefilterFrontEnd.__init__(self,argv=default_argv())
32 # Clean up the namespace for isolation between tests
33 user_ns = self.ipython0.user_ns
34 # We need to keep references to things so that they don't
35 # get garbage collected (this stinks).
36 self.shadow_ns = dict()
37 for i in self.ipython0.magic_who_ls():
38 self.shadow_ns[i] = user_ns.pop(i)
39 # Some more code for isolation (yeah, crazy)
51 # Some more code for isolation (yeah, crazy)
40 self._on_enter()
52 self._on_enter()
41 self.out.flush()
53 self.out.flush()
@@ -52,17 +64,31 b' class TestPrefilterFrontEnd(PrefilterFrontEnd):'
52
64
53 def isolate_ipython0(func):
65 def isolate_ipython0(func):
54 """ Decorator to isolate execution that involves an iptyhon0.
66 """ Decorator to isolate execution that involves an iptyhon0.
67
68 Notes
69 -----
70
71 Apply only to functions with no arguments. Nose skips functions
72 with arguments.
55 """
73 """
56 def my_func(*args, **kwargs):
74 def my_func():
57 ipython0 = get_ipython0().IP
75 iplib = get_ipython0()
58 user_ns = deepcopy(ipython0.user_ns)
76 if iplib is None:
59 global_ns = deepcopy(ipython0.global_ns)
77 return func()
78 ipython0 = iplib.IP
79 global_ns = safe_deepcopy(ipython0.user_global_ns)
80 user_ns = safe_deepcopy(ipython0.user_ns)
60 try:
81 try:
61 func(*args, **kwargs)
82 out = func()
62 finally:
83 finally:
63 ipython0.user_ns = user_ns
84 ipython0.user_ns = user_ns
64 ipython0.global_ns = global_ns
85 ipython0.user_global_ns = global_ns
86 # Undo the hack at creation of PrefilterFrontEnd
87 from IPython import iplib
88 iplib.InteractiveShell.isthreaded = False
89 return out
65
90
91 my_func.__name__ = func.__name__
66 return my_func
92 return my_func
67
93
68
94
@@ -74,7 +100,7 b' def test_execution():'
74 f.input_buffer = 'print 1'
100 f.input_buffer = 'print 1'
75 f._on_enter()
101 f._on_enter()
76 out_value = f.out.getvalue()
102 out_value = f.out.getvalue()
77 assert out_value == '1\n'
103 assert_equal(out_value, '1\n')
78
104
79
105
80 @isolate_ipython0
106 @isolate_ipython0
@@ -87,20 +113,20 b' def test_multiline():'
87 f.input_buffer += 'print 1'
113 f.input_buffer += 'print 1'
88 f._on_enter()
114 f._on_enter()
89 out_value = f.out.getvalue()
115 out_value = f.out.getvalue()
90 assert out_value == ''
116 yield assert_equal, out_value, ''
91 f._on_enter()
117 f._on_enter()
92 out_value = f.out.getvalue()
118 out_value = f.out.getvalue()
93 assert out_value == '1\n'
119 yield assert_equal, out_value, '1\n'
94 f = TestPrefilterFrontEnd()
120 f = TestPrefilterFrontEnd()
95 f.input_buffer='(1 +'
121 f.input_buffer='(1 +'
96 f._on_enter()
122 f._on_enter()
97 f.input_buffer += '0)'
123 f.input_buffer += '0)'
98 f._on_enter()
124 f._on_enter()
99 out_value = f.out.getvalue()
125 out_value = f.out.getvalue()
100 assert out_value == ''
126 yield assert_equal, out_value, ''
101 f._on_enter()
127 f._on_enter()
102 out_value = f.out.getvalue()
128 out_value = f.out.getvalue()
103 assert out_value == '1\n'
129 yield assert_equal, out_value, '1\n'
104
130
105
131
106 @isolate_ipython0
132 @isolate_ipython0
@@ -113,13 +139,13 b' def test_capture():'
113 'import os; out=os.fdopen(1, "w"); out.write("1") ; out.flush()'
139 'import os; out=os.fdopen(1, "w"); out.write("1") ; out.flush()'
114 f._on_enter()
140 f._on_enter()
115 out_value = f.out.getvalue()
141 out_value = f.out.getvalue()
116 assert out_value == '1'
142 yield assert_equal, out_value, '1'
117 f = TestPrefilterFrontEnd()
143 f = TestPrefilterFrontEnd()
118 f.input_buffer = \
144 f.input_buffer = \
119 'import os; out=os.fdopen(2, "w"); out.write("1") ; out.flush()'
145 'import os; out=os.fdopen(2, "w"); out.write("1") ; out.flush()'
120 f._on_enter()
146 f._on_enter()
121 out_value = f.out.getvalue()
147 out_value = f.out.getvalue()
122 assert out_value == '1'
148 yield assert_equal, out_value, '1'
123
149
124
150
125 @isolate_ipython0
151 @isolate_ipython0
@@ -129,10 +155,16 b' def test_magic():'
129 This test is fairly fragile and will break when magics change.
155 This test is fairly fragile and will break when magics change.
130 """
156 """
131 f = TestPrefilterFrontEnd()
157 f = TestPrefilterFrontEnd()
158 # Before checking the interactive namespace, make sure it's clear (it can
159 # otherwise pick up things stored in the user's local db)
160 f.input_buffer += '%reset -f'
161 f._on_enter()
162 f.complete_current_input()
163 # Now, run the %who magic and check output
132 f.input_buffer += '%who'
164 f.input_buffer += '%who'
133 f._on_enter()
165 f._on_enter()
134 out_value = f.out.getvalue()
166 out_value = f.out.getvalue()
135 assert out_value == 'Interactive namespace is empty.\n'
167 assert_equal(out_value, 'Interactive namespace is empty.\n')
136
168
137
169
138 @isolate_ipython0
170 @isolate_ipython0
@@ -156,8 +188,8 b' def test_help():'
156
188
157
189
158 @isolate_ipython0
190 @isolate_ipython0
159 def test_completion():
191 def test_completion_simple():
160 """ Test command-line completion.
192 """ Test command-line completion on trivial examples.
161 """
193 """
162 f = TestPrefilterFrontEnd()
194 f = TestPrefilterFrontEnd()
163 f.input_buffer = 'zzza = 1'
195 f.input_buffer = 'zzza = 1'
@@ -167,8 +199,47 b' def test_completion():'
167 f.input_buffer = 'zz'
199 f.input_buffer = 'zz'
168 f.complete_current_input()
200 f.complete_current_input()
169 out_value = f.out.getvalue()
201 out_value = f.out.getvalue()
170 assert out_value == '\nzzza zzzb '
202 yield assert_equal, out_value, '\nzzza zzzb '
171 assert f.input_buffer == 'zzz'
203 yield assert_equal, f.input_buffer, 'zzz'
204
205
206 @isolate_ipython0
207 def test_completion_parenthesis():
208 """ Test command-line completion when a parenthesis is open.
209 """
210 f = TestPrefilterFrontEnd()
211 f.input_buffer = 'zzza = 1'
212 f._on_enter()
213 f.input_buffer = 'zzzb = 2'
214 f._on_enter()
215 f.input_buffer = 'map(zz'
216 f.complete_current_input()
217 out_value = f.out.getvalue()
218 yield assert_equal, out_value, '\nzzza zzzb '
219 yield assert_equal, f.input_buffer, 'map(zzz'
220
221
222 @isolate_ipython0
223 def test_completion_indexing():
224 """ Test command-line completion when indexing on objects.
225 """
226 f = TestPrefilterFrontEnd()
227 f.input_buffer = 'a = [0]'
228 f._on_enter()
229 f.input_buffer = 'a[0].'
230 f.complete_current_input()
231 assert_equal(f.input_buffer, 'a[0].__')
232
233
234 @isolate_ipython0
235 def test_completion_equal():
236 """ Test command-line completion when the delimiter is "=", not " ".
237 """
238 f = TestPrefilterFrontEnd()
239 f.input_buffer = 'a=1.'
240 f.complete_current_input()
241 assert_equal(f.input_buffer, 'a=1.__')
242
172
243
173
244
174 if __name__ == '__main__':
245 if __name__ == '__main__':
@@ -177,4 +248,5 b" if __name__ == '__main__':"
177 test_execution()
248 test_execution()
178 test_multiline()
249 test_multiline()
179 test_capture()
250 test_capture()
180 test_completion()
251 test_completion_simple()
252 test_completion_complex()
@@ -5,18 +5,18 b' Test process execution and IO redirection.'
5
5
6 __docformat__ = "restructuredtext en"
6 __docformat__ = "restructuredtext en"
7
7
8 #-------------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
9 # Copyright (C) 2008-2009 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is
11 # Distributed under the terms of the BSD License. The full license is
12 # in the file COPYING, distributed as part of this software.
12 # in the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 from cStringIO import StringIO
15 from cStringIO import StringIO
16 from time import sleep
16 from time import sleep
17 import sys
17 import sys
18
18
19 from IPython.frontend._process import PipedProcess
19 from IPython.frontend.process import PipedProcess
20 from IPython.testing import decorators as testdec
20 from IPython.testing import decorators as testdec
21
21
22
22
@@ -25,6 +25,8 b' import wx.stc as stc'
25 from wx.py import editwindow
25 from wx.py import editwindow
26 import time
26 import time
27 import sys
27 import sys
28 import string
29
28 LINESEP = '\n'
30 LINESEP = '\n'
29 if sys.platform == 'win32':
31 if sys.platform == 'win32':
30 LINESEP = '\n\r'
32 LINESEP = '\n\r'
@@ -33,20 +35,26 b' import re'
33
35
34 # FIXME: Need to provide an API for non user-generated display on the
36 # FIXME: Need to provide an API for non user-generated display on the
35 # screen: this should not be editable by the user.
37 # screen: this should not be editable by the user.
38 #-------------------------------------------------------------------------------
39 # Constants
40 #-------------------------------------------------------------------------------
41 _COMPLETE_BUFFER_MARKER = 31
42 _ERROR_MARKER = 30
43 _INPUT_MARKER = 29
36
44
37 _DEFAULT_SIZE = 10
45 _DEFAULT_SIZE = 10
38 if sys.platform == 'darwin':
46 if sys.platform == 'darwin':
39 _DEFAULT_SIZE = 12
47 _DEFAULT_SIZE = 12
40
48
41 _DEFAULT_STYLE = {
49 _DEFAULT_STYLE = {
42 'stdout' : 'fore:#0000FF',
50 #background definition
43 'stderr' : 'fore:#007f00',
44 'trace' : 'fore:#FF0000',
45
46 'default' : 'size:%d' % _DEFAULT_SIZE,
51 'default' : 'size:%d' % _DEFAULT_SIZE,
47 'bracegood' : 'fore:#00AA00,back:#000000,bold',
52 'bracegood' : 'fore:#00AA00,back:#000000,bold',
48 'bracebad' : 'fore:#FF0000,back:#000000,bold',
53 'bracebad' : 'fore:#FF0000,back:#000000,bold',
49
54
55 # Edge column: a number of None
56 'edge_column' : -1,
57
50 # properties for the various Python lexer styles
58 # properties for the various Python lexer styles
51 'comment' : 'fore:#007F00',
59 'comment' : 'fore:#007F00',
52 'number' : 'fore:#007F7F',
60 'number' : 'fore:#007F7F',
@@ -57,7 +65,24 b' _DEFAULT_STYLE = {'
57 'tripledouble' : 'fore:#7F0000',
65 'tripledouble' : 'fore:#7F0000',
58 'class' : 'fore:#0000FF,bold,underline',
66 'class' : 'fore:#0000FF,bold,underline',
59 'def' : 'fore:#007F7F,bold',
67 'def' : 'fore:#007F7F,bold',
60 'operator' : 'bold'
68 'operator' : 'bold',
69
70 # Default colors
71 'trace' : '#FAFAF1', # Nice green
72 'stdout' : '#FDFFD3', # Nice yellow
73 'stderr' : '#FFF1F1', # Nice red
74
75 # Default scintilla settings
76 'antialiasing' : True,
77 'carret_color' : 'BLACK',
78 'background_color' :'WHITE',
79
80 #prompt definition
81 'prompt_in1' : \
82 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02',
83
84 'prompt_out': \
85 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02',
61 }
86 }
62
87
63 # new style numbers
88 # new style numbers
@@ -69,6 +94,47 b' _TRACE_STYLE = 17'
69 # system colors
94 # system colors
70 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
95 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
71
96
97 # Translation table from ANSI escape sequences to color.
98 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
99 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
100 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
101 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
102 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
103 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
104 '1;34': [12, 'LIGHT BLUE'], '1;35':
105 [13, 'MEDIUM VIOLET RED'],
106 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
107
108 # XXX: Maybe one day we should factor this code with ColorANSI. Right now
109 # ColorANSI is hard to reuse and makes our code more complex.
110
111 #we define platform specific fonts
112 if wx.Platform == '__WXMSW__':
113 FACES = { 'times': 'Times New Roman',
114 'mono' : 'Courier New',
115 'helv' : 'Arial',
116 'other': 'Comic Sans MS',
117 'size' : 10,
118 'size2': 8,
119 }
120 elif wx.Platform == '__WXMAC__':
121 FACES = { 'times': 'Times New Roman',
122 'mono' : 'Monaco',
123 'helv' : 'Arial',
124 'other': 'Comic Sans MS',
125 'size' : 10,
126 'size2': 8,
127 }
128 else:
129 FACES = { 'times': 'Times',
130 'mono' : 'Courier',
131 'helv' : 'Helvetica',
132 'other': 'new century schoolbook',
133 'size' : 10,
134 'size2': 8,
135 }
136
137
72 #-------------------------------------------------------------------------------
138 #-------------------------------------------------------------------------------
73 # The console widget class
139 # The console widget class
74 #-------------------------------------------------------------------------------
140 #-------------------------------------------------------------------------------
@@ -83,6 +149,9 b' class ConsoleWidget(editwindow.EditWindow):'
83 # stored.
149 # stored.
84 title = 'Console'
150 title = 'Console'
85
151
152 # Last prompt printed
153 last_prompt = ''
154
86 # The buffer being edited.
155 # The buffer being edited.
87 def _set_input_buffer(self, string):
156 def _set_input_buffer(self, string):
88 self.SetSelection(self.current_prompt_pos, self.GetLength())
157 self.SetSelection(self.current_prompt_pos, self.GetLength())
@@ -103,19 +172,11 b' class ConsoleWidget(editwindow.EditWindow):'
103
172
104 # Translation table from ANSI escape sequences to color. Override
173 # Translation table from ANSI escape sequences to color. Override
105 # this to specify your colors.
174 # this to specify your colors.
106 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
175 ANSI_STYLES = ANSI_STYLES.copy()
107 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
108 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
109 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
110 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
111 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
112 '1;34': [12, 'LIGHT BLUE'], '1;35':
113 [13, 'MEDIUM VIOLET RED'],
114 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
115
116 # The color of the carret (call _apply_style() after setting)
117 carret_color = 'BLACK'
118
176
177 # Font faces
178 faces = FACES.copy()
179
119 # Store the last time a refresh was done
180 # Store the last time a refresh was done
120 _last_refresh_time = 0
181 _last_refresh_time = 0
121
182
@@ -126,7 +187,11 b' class ConsoleWidget(editwindow.EditWindow):'
126 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
187 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
127 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
188 size=wx.DefaultSize, style=wx.WANTS_CHARS, ):
128 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
189 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
129 self._configure_scintilla()
190 self.configure_scintilla()
191 # Track if 'enter' key as ever been processed
192 # This variable will only be reallowed until key goes up
193 self.enter_catched = False
194 self.current_prompt_pos = 0
130
195
131 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
196 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
132 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
197 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
@@ -193,8 +258,19 b' class ConsoleWidget(editwindow.EditWindow):'
193 self.current_prompt_pos = self.GetLength()
258 self.current_prompt_pos = self.GetLength()
194 self.current_prompt_line = self.GetCurrentLine()
259 self.current_prompt_line = self.GetCurrentLine()
195 self.EnsureCaretVisible()
260 self.EnsureCaretVisible()
261 self.last_prompt = prompt
196
262
197
263
264 def continuation_prompt(self):
265 """ Returns the current continuation prompt.
266 We need to implement this method here to deal with the
267 ascii escape sequences cleaning up.
268 """
269 # ASCII-less prompt
270 ascii_less = ''.join(self.color_pat.split(self.last_prompt)[2::2])
271 return "."*(len(ascii_less)-2) + ': '
272
273
198 def scroll_to_bottom(self):
274 def scroll_to_bottom(self):
199 maxrange = self.GetScrollRange(wx.VERTICAL)
275 maxrange = self.GetScrollRange(wx.VERTICAL)
200 self.ScrollLines(maxrange)
276 self.ScrollLines(maxrange)
@@ -216,37 +292,24 b' class ConsoleWidget(editwindow.EditWindow):'
216 """
292 """
217 return self.GetSize()[0]/self.GetCharWidth()
293 return self.GetSize()[0]/self.GetCharWidth()
218
294
219 #--------------------------------------------------------------------------
220 # EditWindow API
221 #--------------------------------------------------------------------------
222
295
223 def OnUpdateUI(self, event):
296 def configure_scintilla(self):
224 """ Override the OnUpdateUI of the EditWindow class, to prevent
297 """ Set up all the styling option of the embedded scintilla
225 syntax highlighting both for faster redraw, and for more
298 widget.
226 consistent look and feel.
227 """
299 """
300 p = self.style.copy()
301
302 # Marker for complete buffer.
303 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
304 background=p['trace'])
228
305
229 #--------------------------------------------------------------------------
306 # Marker for current input buffer.
230 # Private API
307 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
231 #--------------------------------------------------------------------------
308 background=p['stdout'])
232
309 # Marker for tracebacks.
233 def _apply_style(self):
310 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
234 """ Applies the colors for the different text elements and the
311 background=p['stderr'])
235 carret.
236 """
237 self.SetCaretForeground(self.carret_color)
238
239 #self.StyleClearAll()
240 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
241 "fore:#FF0000,back:#0000FF,bold")
242 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
243 "fore:#000000,back:#FF0000,bold")
244
245 for style in self.ANSI_STYLES.values():
246 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
247
312
248
249 def _configure_scintilla(self):
250 self.SetEOLMode(stc.STC_EOL_LF)
313 self.SetEOLMode(stc.STC_EOL_LF)
251
314
252 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
315 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
@@ -268,7 +331,9 b' class ConsoleWidget(editwindow.EditWindow):'
268 self.SetWrapMode(stc.STC_WRAP_CHAR)
331 self.SetWrapMode(stc.STC_WRAP_CHAR)
269 self.SetWrapMode(stc.STC_WRAP_WORD)
332 self.SetWrapMode(stc.STC_WRAP_WORD)
270 self.SetBufferedDraw(True)
333 self.SetBufferedDraw(True)
271 self.SetUseAntiAliasing(True)
334
335 self.SetUseAntiAliasing(p['antialiasing'])
336
272 self.SetLayoutCache(stc.STC_CACHE_PAGE)
337 self.SetLayoutCache(stc.STC_CACHE_PAGE)
273 self.SetUndoCollection(False)
338 self.SetUndoCollection(False)
274 self.SetUseTabs(True)
339 self.SetUseTabs(True)
@@ -289,23 +354,48 b' class ConsoleWidget(editwindow.EditWindow):'
289 self.SetMarginWidth(1, 0)
354 self.SetMarginWidth(1, 0)
290 self.SetMarginWidth(2, 0)
355 self.SetMarginWidth(2, 0)
291
356
292 self._apply_style()
293
294 # Xterm escape sequences
357 # Xterm escape sequences
295 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
358 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
296 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
359 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
297
360
298 #self.SetEdgeMode(stc.STC_EDGE_LINE)
299 #self.SetEdgeColumn(80)
300
301 # styles
361 # styles
302 p = self.style
362
303 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
363 self.SetCaretForeground(p['carret_color'])
364
365 background_color = p['background_color']
366
367 if 'default' in p:
368 if 'back' not in p['default']:
369 p['default'] += ',back:%s' % background_color
370 if 'size' not in p['default']:
371 p['default'] += ',size:%s' % self.faces['size']
372 if 'face' not in p['default']:
373 p['default'] += ',face:%s' % self.faces['mono']
374
375 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
376 else:
377 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
378 "fore:%s,back:%s,size:%d,face:%s"
379 % (self.ANSI_STYLES['0;30'][1],
380 background_color,
381 self.faces['size'], self.faces['mono']))
382
304 self.StyleClearAll()
383 self.StyleClearAll()
384
385 # XXX: two lines below are usefull if not using the lexer
386 #for style in self.ANSI_STYLES.values():
387 # self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
388
389 # prompt definition
390 self.prompt_in1 = p['prompt_in1']
391 self.prompt_out = p['prompt_out']
392
393 self.output_prompt_template = string.Template(self.prompt_out)
394 self.input_prompt_template = string.Template(self.prompt_in1)
395
305 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
396 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
306 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
397 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
307 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
398 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
308
309 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
399 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
310 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
400 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
311 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
401 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
@@ -321,6 +411,28 b' class ConsoleWidget(editwindow.EditWindow):'
321 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
411 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
322 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
412 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
323
413
414 edge_column = p['edge_column']
415 if edge_column is not None and edge_column > 0:
416 #we add a vertical line to console widget
417 self.SetEdgeMode(stc.STC_EDGE_LINE)
418 self.SetEdgeColumn(edge_column)
419
420
421 #--------------------------------------------------------------------------
422 # EditWindow API
423 #--------------------------------------------------------------------------
424
425 def OnUpdateUI(self, event):
426 """ Override the OnUpdateUI of the EditWindow class, to prevent
427 syntax highlighting both for faster redraw, and for more
428 consistent look and feel.
429 """
430
431
432 #--------------------------------------------------------------------------
433 # Private API
434 #--------------------------------------------------------------------------
435
324 def _on_key_down(self, event, skip=True):
436 def _on_key_down(self, event, skip=True):
325 """ Key press callback used for correcting behavior for
437 """ Key press callback used for correcting behavior for
326 console-like interfaces: the cursor is constraint to be after
438 console-like interfaces: the cursor is constraint to be after
@@ -329,6 +441,11 b' class ConsoleWidget(editwindow.EditWindow):'
329 Return True if event as been catched.
441 Return True if event as been catched.
330 """
442 """
331 catched = True
443 catched = True
444 # XXX: Would the right way to do this be to have a
445 # dictionary at the instance level associating keys with
446 # callbacks? How would we deal with inheritance? And Do the
447 # different callbacks share local variables?
448
332 # Intercept some specific keys.
449 # Intercept some specific keys.
333 if event.KeyCode == ord('L') and event.ControlDown() :
450 if event.KeyCode == ord('L') and event.ControlDown() :
334 self.scroll_to_bottom()
451 self.scroll_to_bottom()
@@ -346,6 +463,10 b' class ConsoleWidget(editwindow.EditWindow):'
346 self.ScrollPages(-1)
463 self.ScrollPages(-1)
347 elif event.KeyCode == wx.WXK_PAGEDOWN:
464 elif event.KeyCode == wx.WXK_PAGEDOWN:
348 self.ScrollPages(1)
465 self.ScrollPages(1)
466 elif event.KeyCode == wx.WXK_HOME:
467 self.GotoPos(self.GetLength())
468 elif event.KeyCode == wx.WXK_END:
469 self.GotoPos(self.GetLength())
349 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
470 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
350 self.ScrollLines(-1)
471 self.ScrollLines(-1)
351 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
472 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
@@ -357,16 +478,20 b' class ConsoleWidget(editwindow.EditWindow):'
357 event.Skip()
478 event.Skip()
358 else:
479 else:
359 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
480 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
360 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
481 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN,
482 wx.MOD_SHIFT):
361 catched = True
483 catched = True
362 self.CallTipCancel()
484 if not self.enter_catched:
363 self.write('\n', refresh=False)
485 self.CallTipCancel()
364 # Under windows scintilla seems to be doing funny stuff to the
486 if event.Modifiers == wx.MOD_SHIFT:
365 # line returns here, but the getter for input_buffer filters
487 # Try to force execution
366 # this out.
488 self.GotoPos(self.GetLength())
367 if sys.platform == 'win32':
489 self.write('\n' + self.continuation_prompt(),
368 self.input_buffer = self.input_buffer
490 refresh=False)
369 self._on_enter()
491 self._on_enter()
492 else:
493 self._on_enter()
494 self.enter_catched = True
370
495
371 elif event.KeyCode == wx.WXK_HOME:
496 elif event.KeyCode == wx.WXK_HOME:
372 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
497 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
@@ -391,16 +516,28 b' class ConsoleWidget(editwindow.EditWindow):'
391 catched = True
516 catched = True
392
517
393 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
518 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
394 if self.GetCurrentPos() > self.current_prompt_pos:
519 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
520 event.Skip()
521 catched = True
522
523 elif event.KeyCode == wx.WXK_RIGHT:
524 if not self._keep_cursor_in_buffer(self.GetCurrentPos() + 1):
525 event.Skip()
526 catched = True
527
528
529 elif event.KeyCode == wx.WXK_DELETE:
530 if not self._keep_cursor_in_buffer(self.GetCurrentPos() - 1):
395 event.Skip()
531 event.Skip()
396 catched = True
532 catched = True
397
533
398 if skip and not catched:
534 if skip and not catched:
399 # Put the cursor back in the edit region
535 # Put the cursor back in the edit region
400 if self.GetCurrentPos() < self.current_prompt_pos:
536 if not self._keep_cursor_in_buffer():
401 self.GotoPos(self.current_prompt_pos)
537 if not (self.GetCurrentPos() == self.GetLength()
402 else:
538 and event.KeyCode == wx.WXK_DELETE):
403 event.Skip()
539 event.Skip()
540 catched = True
404
541
405 return catched
542 return catched
406
543
@@ -408,17 +545,69 b' class ConsoleWidget(editwindow.EditWindow):'
408 def _on_key_up(self, event, skip=True):
545 def _on_key_up(self, event, skip=True):
409 """ If cursor is outside the editing region, put it back.
546 """ If cursor is outside the editing region, put it back.
410 """
547 """
411 event.Skip()
548 if skip:
412 if self.GetCurrentPos() < self.current_prompt_pos:
549 event.Skip()
413 self.GotoPos(self.current_prompt_pos)
550 self._keep_cursor_in_buffer()
551
552
553 # XXX: I need to avoid the problem of having an empty glass;
554 def _keep_cursor_in_buffer(self, pos=None):
555 """ Checks if the cursor is where it is allowed to be. If not,
556 put it back.
414
557
558 Returns
559 -------
560 cursor_moved: Boolean
561 whether or not the cursor was moved by this routine.
562
563 Notes
564 ------
565 WARNING: This does proper checks only for horizontal
566 movements.
567 """
568 if pos is None:
569 current_pos = self.GetCurrentPos()
570 else:
571 current_pos = pos
572 if current_pos < self.current_prompt_pos:
573 self.GotoPos(self.current_prompt_pos)
574 return True
575 line_num = self.LineFromPosition(current_pos)
576 if not current_pos > self.GetLength():
577 line_pos = self.GetColumn(current_pos)
578 else:
579 line_pos = self.GetColumn(self.GetLength())
580 line = self.GetLine(line_num)
581 # Jump the continuation prompt
582 continuation_prompt = self.continuation_prompt()
583 if ( line.startswith(continuation_prompt)
584 and line_pos < len(continuation_prompt)):
585 if line_pos < 2:
586 # We are at the beginning of the line, trying to move
587 # forward: jump forward.
588 self.GotoPos(current_pos + 1 +
589 len(continuation_prompt) - line_pos)
590 else:
591 # Jump back up
592 self.GotoPos(self.GetLineEndPosition(line_num-1))
593 return True
594 elif ( current_pos > self.GetLineEndPosition(line_num)
595 and not current_pos == self.GetLength()):
596 # Jump to next line
597 self.GotoPos(current_pos + 1 +
598 len(continuation_prompt))
599 return True
600
601 # We re-allow enter event processing
602 self.enter_catched = False
603 return False
415
604
416
605
417 if __name__ == '__main__':
606 if __name__ == '__main__':
418 # Some simple code to test the console widget.
607 # Some simple code to test the console widget.
419 class MainWindow(wx.Frame):
608 class MainWindow(wx.Frame):
420 def __init__(self, parent, id, title):
609 def __init__(self, parent, id, title):
421 wx.Frame.__init__(self, parent, id, title, size=(300,250))
610 wx.Frame.__init__(self, parent, id, title, size=(300, 250))
422 self._sizer = wx.BoxSizer(wx.VERTICAL)
611 self._sizer = wx.BoxSizer(wx.VERTICAL)
423 self.console_widget = ConsoleWidget(self)
612 self.console_widget = ConsoleWidget(self)
424 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
613 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
@@ -80,6 +80,15 b' class IPythonX(wx.Frame):'
80 self.SetSizer(self._sizer)
80 self.SetSizer(self._sizer)
81 self.SetAutoLayout(1)
81 self.SetAutoLayout(1)
82 self.Show(True)
82 self.Show(True)
83 wx.EVT_CLOSE(self, self.on_close)
84
85
86 def on_close(self, event):
87 """ Called on closing the windows.
88
89 Stops the event loop, to close all the child windows.
90 """
91 wx.CallAfter(wx.Exit)
83
92
84
93
85 def main():
94 def main():
@@ -25,38 +25,19 b' __docformat__ = "restructuredtext en"'
25 # Major library imports
25 # Major library imports
26 import re
26 import re
27 import __builtin__
27 import __builtin__
28 from time import sleep
29 import sys
28 import sys
30 from threading import Lock
29 from threading import Lock
31 import string
32
30
33 import wx
31 import wx
34 from wx import stc
32 from wx import stc
35
33
36 # Ipython-specific imports.
34 # Ipython-specific imports.
37 from IPython.frontend._process import PipedProcess
35 from IPython.frontend.process import PipedProcess
38 from console_widget import ConsoleWidget
36 from console_widget import ConsoleWidget, _COMPLETE_BUFFER_MARKER, \
37 _ERROR_MARKER, _INPUT_MARKER
39 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
38 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
40
39
41 #-------------------------------------------------------------------------------
40 #-------------------------------------------------------------------------------
42 # Constants
43 #-------------------------------------------------------------------------------
44
45 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
46 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
47 _ERROR_BG = '#FFF1F1' # Nice red
48
49 _COMPLETE_BUFFER_MARKER = 31
50 _ERROR_MARKER = 30
51 _INPUT_MARKER = 29
52
53 prompt_in1 = \
54 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
55
56 prompt_out = \
57 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
58
59 #-------------------------------------------------------------------------------
60 # Classes to implement the Wx frontend
41 # Classes to implement the Wx frontend
61 #-------------------------------------------------------------------------------
42 #-------------------------------------------------------------------------------
62 class WxController(ConsoleWidget, PrefilterFrontEnd):
43 class WxController(ConsoleWidget, PrefilterFrontEnd):
@@ -66,11 +47,7 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
66 This class inherits from ConsoleWidget, that provides a console-like
47 This class inherits from ConsoleWidget, that provides a console-like
67 widget to provide a text-rendering widget suitable for a terminal.
48 widget to provide a text-rendering widget suitable for a terminal.
68 """
49 """
69
50
70 output_prompt_template = string.Template(prompt_out)
71
72 input_prompt_template = string.Template(prompt_in1)
73
74 # Print debug info on what is happening to the console.
51 # Print debug info on what is happening to the console.
75 debug = False
52 debug = False
76
53
@@ -138,25 +115,24 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
138 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
115 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
139 size=wx.DefaultSize,
116 size=wx.DefaultSize,
140 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
117 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
118 styledef=None,
141 *args, **kwds):
119 *args, **kwds):
142 """ Create Shell instance.
120 """ Create Shell instance.
121
122 Parameters
123 -----------
124 styledef : dict, optional
125 styledef is the dictionary of options used to define the
126 style.
143 """
127 """
128 if styledef is not None:
129 self.style = styledef
144 ConsoleWidget.__init__(self, parent, id, pos, size, style)
130 ConsoleWidget.__init__(self, parent, id, pos, size, style)
145 PrefilterFrontEnd.__init__(self, **kwds)
131 PrefilterFrontEnd.__init__(self, **kwds)
146
132
147 # Stick in our own raw_input:
133 # Stick in our own raw_input:
148 self.ipython0.raw_input = self.raw_input
134 self.ipython0.raw_input = self.raw_input
149
135
150 # Marker for complete buffer.
151 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
152 background=_COMPLETE_BUFFER_BG)
153 # Marker for current input buffer.
154 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
155 background=_INPUT_BUFFER_BG)
156 # Marker for tracebacks.
157 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
158 background=_ERROR_BG)
159
160 # A time for flushing the write buffer
136 # A time for flushing the write buffer
161 BUFFER_FLUSH_TIMER_ID = 100
137 BUFFER_FLUSH_TIMER_ID = 100
162 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
138 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
@@ -171,8 +147,7 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
171 self.shell.user_ns['self'] = self
147 self.shell.user_ns['self'] = self
172 # Inject our own raw_input in namespace
148 # Inject our own raw_input in namespace
173 self.shell.user_ns['raw_input'] = self.raw_input
149 self.shell.user_ns['raw_input'] = self.raw_input
174
150
175
176 def raw_input(self, prompt=''):
151 def raw_input(self, prompt=''):
177 """ A replacement from python's raw_input.
152 """ A replacement from python's raw_input.
178 """
153 """
@@ -251,11 +226,8 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
251 if (self.AutoCompActive() and line and not line[-1] == '.') \
226 if (self.AutoCompActive() and line and not line[-1] == '.') \
252 or create==True:
227 or create==True:
253 suggestion, completions = self.complete(line)
228 suggestion, completions = self.complete(line)
254 offset=0
255 if completions:
229 if completions:
256 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
230 offset = len(self._get_completion_text(line))
257 residual = complete_sep.split(line)[-1]
258 offset = len(residual)
259 self.pop_completion(completions, offset=offset)
231 self.pop_completion(completions, offset=offset)
260 if self.debug:
232 if self.debug:
261 print >>sys.__stdout__, completions
233 print >>sys.__stdout__, completions
@@ -276,6 +248,14 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
276 milliseconds=100, oneShot=True)
248 milliseconds=100, oneShot=True)
277
249
278
250
251 def clear_screen(self):
252 """ Empty completely the widget.
253 """
254 self.ClearAll()
255 self.new_prompt(self.input_prompt_template.substitute(
256 number=(self.last_result['number'] + 1)))
257
258
279 #--------------------------------------------------------------------------
259 #--------------------------------------------------------------------------
280 # LineFrontEnd interface
260 # LineFrontEnd interface
281 #--------------------------------------------------------------------------
261 #--------------------------------------------------------------------------
@@ -299,6 +279,41 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
299 raw_string=raw_string)
279 raw_string=raw_string)
300 wx.CallAfter(callback)
280 wx.CallAfter(callback)
301
281
282
283 def execute_command(self, command, hidden=False):
284 """ Execute a command, not only in the model, but also in the
285 view.
286 """
287 # XXX: This method needs to be integrated in the base fronted
288 # interface
289 if hidden:
290 return self.shell.execute(command)
291 else:
292 # XXX: we are not storing the input buffer previous to the
293 # execution, as this forces us to run the execution
294 # input_buffer a yield, which is not good.
295 ##current_buffer = self.shell.control.input_buffer
296 command = command.rstrip()
297 if len(command.split('\n')) > 1:
298 # The input command is several lines long, we need to
299 # force the execution to happen
300 command += '\n'
301 cleaned_command = self.prefilter_input(command)
302 self.input_buffer = command
303 # Do not use wx.Yield() (aka GUI.process_events()) to avoid
304 # recursive yields.
305 self.ProcessEvent(wx.PaintEvent())
306 self.write('\n')
307 if not self.is_complete(cleaned_command + '\n'):
308 self._colorize_input_buffer()
309 self.render_error('Incomplete or invalid input')
310 self.new_prompt(self.input_prompt_template.substitute(
311 number=(self.last_result['number'] + 1)))
312 return False
313 self._on_enter()
314 return True
315
316
302 def save_output_hooks(self):
317 def save_output_hooks(self):
303 self.__old_raw_input = __builtin__.raw_input
318 self.__old_raw_input = __builtin__.raw_input
304 PrefilterFrontEnd.save_output_hooks(self)
319 PrefilterFrontEnd.save_output_hooks(self)
@@ -356,10 +371,16 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
356 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
371 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
357
372
358
373
374 def continuation_prompt(self, *args, **kwargs):
375 # Avoid multiple inheritence, be explicit about which
376 # parent method class gets called
377 return ConsoleWidget.continuation_prompt(self, *args, **kwargs)
378
379
359 def write(self, *args, **kwargs):
380 def write(self, *args, **kwargs):
360 # Avoid multiple inheritence, be explicit about which
381 # Avoid multiple inheritence, be explicit about which
361 # parent method class gets called
382 # parent method class gets called
362 ConsoleWidget.write(self, *args, **kwargs)
383 return ConsoleWidget.write(self, *args, **kwargs)
363
384
364
385
365 def _on_key_down(self, event, skip=True):
386 def _on_key_down(self, event, skip=True):
@@ -367,7 +388,7 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
367 widget handle them, and put our logic afterward.
388 widget handle them, and put our logic afterward.
368 """
389 """
369 # FIXME: This method needs to be broken down in smaller ones.
390 # FIXME: This method needs to be broken down in smaller ones.
370 current_line_number = self.GetCurrentLine()
391 current_line_num = self.GetCurrentLine()
371 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
392 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
372 # Capture Control-C
393 # Capture Control-C
373 if self._input_state == 'subprocess':
394 if self._input_state == 'subprocess':
@@ -413,7 +434,7 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
413 else:
434 else:
414 # Up history
435 # Up history
415 if event.KeyCode == wx.WXK_UP and (
436 if event.KeyCode == wx.WXK_UP and (
416 ( current_line_number == self.current_prompt_line and
437 ( current_line_num == self.current_prompt_line and
417 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
438 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
418 or event.ControlDown() ):
439 or event.ControlDown() ):
419 new_buffer = self.get_history_previous(
440 new_buffer = self.get_history_previous(
@@ -425,7 +446,7 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
425 self.GotoPos(self.current_prompt_pos)
446 self.GotoPos(self.current_prompt_pos)
426 # Down history
447 # Down history
427 elif event.KeyCode == wx.WXK_DOWN and (
448 elif event.KeyCode == wx.WXK_DOWN and (
428 ( current_line_number == self.LineCount -1 and
449 ( current_line_num == self.LineCount -1 and
429 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
450 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
430 or event.ControlDown() ):
451 or event.ControlDown() ):
431 new_buffer = self.get_history_next()
452 new_buffer = self.get_history_next()
@@ -433,15 +454,43 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
433 self.input_buffer = new_buffer
454 self.input_buffer = new_buffer
434 # Tab-completion
455 # Tab-completion
435 elif event.KeyCode == ord('\t'):
456 elif event.KeyCode == ord('\t'):
436 current_line, current_line_number = self.CurLine
457 current_line, current_line_num = self.CurLine
437 if not re.match(r'^\s*$', current_line):
458 if not re.match(r'^\s*$', current_line):
438 self.complete_current_input()
459 self.complete_current_input()
439 if self.AutoCompActive():
460 if self.AutoCompActive():
440 wx.CallAfter(self._popup_completion, create=True)
461 wx.CallAfter(self._popup_completion, create=True)
441 else:
462 else:
442 event.Skip()
463 event.Skip()
464 elif event.KeyCode == wx.WXK_BACK:
465 # If characters where erased, check if we have to
466 # remove a line.
467 # XXX: What about DEL?
468 # FIXME: This logics should be in ConsoleWidget, as it is
469 # independant of IPython
470 current_line, _ = self.CurLine
471 current_pos = self.GetCurrentPos()
472 current_line_num = self.LineFromPosition(current_pos)
473 current_col = self.GetColumn(current_pos)
474 len_prompt = len(self.continuation_prompt())
475 if ( current_line.startswith(self.continuation_prompt())
476 and current_col == len_prompt):
477 new_lines = []
478 for line_num, line in enumerate(
479 self.input_buffer.split('\n')):
480 if (line_num + self.current_prompt_line ==
481 current_line_num):
482 new_lines.append(line[len_prompt:])
483 else:
484 new_lines.append('\n'+line)
485 # The first character is '\n', due to the above
486 # code:
487 self.input_buffer = ''.join(new_lines)[1:]
488 self.GotoPos(current_pos - 1 - len_prompt)
489 else:
490 ConsoleWidget._on_key_down(self, event, skip=skip)
443 else:
491 else:
444 ConsoleWidget._on_key_down(self, event, skip=skip)
492 ConsoleWidget._on_key_down(self, event, skip=skip)
493
445
494
446
495
447 def _on_key_up(self, event, skip=True):
496 def _on_key_up(self, event, skip=True):
@@ -453,14 +502,40 b' class WxController(ConsoleWidget, PrefilterFrontEnd):'
453 wx.CallAfter(self._popup_completion, create=True)
502 wx.CallAfter(self._popup_completion, create=True)
454 else:
503 else:
455 ConsoleWidget._on_key_up(self, event, skip=skip)
504 ConsoleWidget._on_key_up(self, event, skip=skip)
505 # Make sure the continuation_prompts are always followed by a
506 # whitespace
507 new_lines = []
508 if self._input_state == 'readline':
509 position = self.GetCurrentPos()
510 continuation_prompt = self.continuation_prompt()[:-1]
511 for line in self.input_buffer.split('\n'):
512 if not line == continuation_prompt:
513 new_lines.append(line)
514 self.input_buffer = '\n'.join(new_lines)
515 self.GotoPos(position)
456
516
457
517
458 def _on_enter(self):
518 def _on_enter(self):
459 """ Called on return key down, in readline input_state.
519 """ Called on return key down, in readline input_state.
460 """
520 """
521 last_line_num = self.LineFromPosition(self.GetLength())
522 current_line_num = self.LineFromPosition(self.GetCurrentPos())
523 new_line_pos = (last_line_num - current_line_num)
461 if self.debug:
524 if self.debug:
462 print >>sys.__stdout__, repr(self.input_buffer)
525 print >>sys.__stdout__, repr(self.input_buffer)
463 PrefilterFrontEnd._on_enter(self)
526 self.write('\n', refresh=False)
527 # Under windows scintilla seems to be doing funny
528 # stuff to the line returns here, but the getter for
529 # input_buffer filters this out.
530 if sys.platform == 'win32':
531 self.input_buffer = self.input_buffer
532 old_prompt_num = self.current_prompt_pos
533 has_executed = PrefilterFrontEnd._on_enter(self,
534 new_line_pos=new_line_pos)
535 if old_prompt_num == self.current_prompt_pos:
536 # No execution has happened
537 self.GotoPos(self.GetLineEndPosition(current_line_num + 1))
538 return has_executed
464
539
465
540
466 #--------------------------------------------------------------------------
541 #--------------------------------------------------------------------------
@@ -1007,7 +1007,17 b' def get_security_dir():'
1007 else:
1007 else:
1008 os.chmod(security_dir, 0700)
1008 os.chmod(security_dir, 0700)
1009 return security_dir
1009 return security_dir
1010
1010
1011 def get_log_dir():
1012 """Get the IPython log directory.
1013
1014 If the log directory does not exist, it is created.
1015 """
1016 log_dir = os.path.join(get_ipython_dir(), 'log')
1017 if not os.path.isdir(log_dir):
1018 os.mkdir(log_dir, 0777)
1019 return log_dir
1020
1011 #****************************************************************************
1021 #****************************************************************************
1012 # strings and text
1022 # strings and text
1013
1023
@@ -54,7 +54,7 b' from pprint import pprint, pformat'
54 from IPython import Debugger,OInspect,PyColorize,ultraTB
54 from IPython import Debugger,OInspect,PyColorize,ultraTB
55 from IPython.ColorANSI import ColorScheme,ColorSchemeTable # too long names
55 from IPython.ColorANSI import ColorScheme,ColorSchemeTable # too long names
56 from IPython.Extensions import pickleshare
56 from IPython.Extensions import pickleshare
57 from IPython.FakeModule import FakeModule
57 from IPython.FakeModule import FakeModule, init_fakemod_dict
58 from IPython.Itpl import Itpl,itpl,printpl,ItplNS,itplns
58 from IPython.Itpl import Itpl,itpl,printpl,ItplNS,itplns
59 from IPython.Logger import Logger
59 from IPython.Logger import Logger
60 from IPython.Magic import Magic
60 from IPython.Magic import Magic
@@ -108,6 +108,201 b' def softspace(file, newvalue):'
108 return oldvalue
108 return oldvalue
109
109
110
110
111 def user_setup(ipythondir,rc_suffix,mode='install',interactive=True):
112 """Install or upgrade the user configuration directory.
113
114 Can be called when running for the first time or to upgrade the user's
115 .ipython/ directory.
116
117 Parameters
118 ----------
119 ipythondir : path
120 The directory to be used for installation/upgrade. In 'install' mode,
121 if this path already exists, the function exits immediately.
122
123 rc_suffix : str
124 Extension for the config files. On *nix platforms it is typically the
125 empty string, while Windows normally uses '.ini'.
126
127 mode : str, optional
128 Valid modes are 'install' and 'upgrade'.
129
130 interactive : bool, optional
131 If False, do not wait for user input on any errors. Normally after
132 printing its status information, this function waits for the user to
133 hit Return before proceeding. This is because the default use case is
134 when first installing the IPython configuration, so we want the user to
135 acknowledge the initial message, which contains some useful
136 information.
137 """
138
139 # For automatic use, deactivate all i/o
140 if interactive:
141 def wait():
142 try:
143 raw_input("Please press <RETURN> to start IPython.")
144 except EOFError:
145 print >> Term.cout
146 print '*'*70
147
148 def printf(s):
149 print s
150 else:
151 wait = lambda : None
152 printf = lambda s : None
153
154 # Install mode should be re-entrant: if the install dir already exists,
155 # bail out cleanly.
156 # XXX. This is too hasty to return. We need to check to make sure that
157 # all the expected config files and directories are actually there. We
158 # currently have a failure mode if someone deletes a needed config file
159 # but still has the ipythondir.
160 if mode == 'install' and os.path.isdir(ipythondir):
161 return
162
163 cwd = os.getcwd() # remember where we started
164 glb = glob.glob
165
166 printf('*'*70)
167 if mode == 'install':
168 printf(
169 """Welcome to IPython. I will try to create a personal configuration directory
170 where you can customize many aspects of IPython's functionality in:\n""")
171 else:
172 printf('I am going to upgrade your configuration in:')
173
174 printf(ipythondir)
175
176 rcdirend = os.path.join('IPython','UserConfig')
177 cfg = lambda d: os.path.join(d,rcdirend)
178 try:
179 rcdir = filter(os.path.isdir,map(cfg,sys.path))[0]
180 printf("Initializing from configuration: %s" % rcdir)
181 except IndexError:
182 warning = """
183 Installation error. IPython's directory was not found.
184
185 Check the following:
186
187 The ipython/IPython directory should be in a directory belonging to your
188 PYTHONPATH environment variable (that is, it should be in a directory
189 belonging to sys.path). You can copy it explicitly there or just link to it.
190
191 IPython will create a minimal default configuration for you.
192
193 """
194 warn(warning)
195 wait()
196
197 if sys.platform =='win32':
198 inif = 'ipythonrc.ini'
199 else:
200 inif = 'ipythonrc'
201 minimal_setup = {'ipy_user_conf.py' : 'import ipy_defaults',
202 inif : '# intentionally left blank' }
203 os.makedirs(ipythondir, mode = 0777)
204 for f, cont in minimal_setup.items():
205 # In 2.5, this can be more cleanly done using 'with'
206 fobj = file(ipythondir + '/' + f,'w')
207 fobj.write(cont)
208 fobj.close()
209
210 return
211
212 if mode == 'install':
213 try:
214 shutil.copytree(rcdir,ipythondir)
215 os.chdir(ipythondir)
216 rc_files = glb("ipythonrc*")
217 for rc_file in rc_files:
218 os.rename(rc_file,rc_file+rc_suffix)
219 except:
220 warning = """
221
222 There was a problem with the installation:
223 %s
224 Try to correct it or contact the developers if you think it's a bug.
225 IPython will proceed with builtin defaults.""" % sys.exc_info()[1]
226 warn(warning)
227 wait()
228 return
229
230 elif mode == 'upgrade':
231 try:
232 os.chdir(ipythondir)
233 except:
234 printf("""
235 Can not upgrade: changing to directory %s failed. Details:
236 %s
237 """ % (ipythondir,sys.exc_info()[1]) )
238 wait()
239 return
240 else:
241 sources = glb(os.path.join(rcdir,'[A-Za-z]*'))
242 for new_full_path in sources:
243 new_filename = os.path.basename(new_full_path)
244 if new_filename.startswith('ipythonrc'):
245 new_filename = new_filename + rc_suffix
246 # The config directory should only contain files, skip any
247 # directories which may be there (like CVS)
248 if os.path.isdir(new_full_path):
249 continue
250 if os.path.exists(new_filename):
251 old_file = new_filename+'.old'
252 if os.path.exists(old_file):
253 os.remove(old_file)
254 os.rename(new_filename,old_file)
255 shutil.copy(new_full_path,new_filename)
256 else:
257 raise ValueError('unrecognized mode for install: %r' % mode)
258
259 # Fix line-endings to those native to each platform in the config
260 # directory.
261 try:
262 os.chdir(ipythondir)
263 except:
264 printf("""
265 Problem: changing to directory %s failed.
266 Details:
267 %s
268
269 Some configuration files may have incorrect line endings. This should not
270 cause any problems during execution. """ % (ipythondir,sys.exc_info()[1]) )
271 wait()
272 else:
273 for fname in glb('ipythonrc*'):
274 try:
275 native_line_ends(fname,backup=0)
276 except IOError:
277 pass
278
279 if mode == 'install':
280 printf("""
281 Successful installation!
282
283 Please read the sections 'Initial Configuration' and 'Quick Tips' in the
284 IPython manual (there are both HTML and PDF versions supplied with the
285 distribution) to make sure that your system environment is properly configured
286 to take advantage of IPython's features.
287
288 Important note: the configuration system has changed! The old system is
289 still in place, but its setting may be partly overridden by the settings in
290 "~/.ipython/ipy_user_conf.py" config file. Please take a look at the file
291 if some of the new settings bother you.
292
293 """)
294 else:
295 printf("""
296 Successful upgrade!
297
298 All files in your directory:
299 %(ipythondir)s
300 which would have been overwritten by the upgrade were backed up with a .old
301 extension. If you had made particular customizations in those files you may
302 want to merge them back into the new files.""" % locals() )
303 wait()
304 os.chdir(cwd)
305
111 #****************************************************************************
306 #****************************************************************************
112 # Local use exceptions
307 # Local use exceptions
113 class SpaceInInput(exceptions.Exception): pass
308 class SpaceInInput(exceptions.Exception): pass
@@ -308,13 +503,24 b' class InteractiveShell(object,Magic):'
308 # calling functions defined in the script that use other things from
503 # calling functions defined in the script that use other things from
309 # the script will fail, because the function's closure had references
504 # the script will fail, because the function's closure had references
310 # to the original objects, which are now all None. So we must protect
505 # to the original objects, which are now all None. So we must protect
311 # these modules from deletion by keeping a cache. To avoid keeping
506 # these modules from deletion by keeping a cache.
312 # stale modules around (we only need the one from the last run), we use
507 #
313 # a dict keyed with the full path to the script, so only the last
508 # To avoid keeping stale modules around (we only need the one from the
314 # version of the module is held in the cache. The %reset command will
509 # last run), we use a dict keyed with the full path to the script, so
315 # flush this cache. See the cache_main_mod() and clear_main_mod_cache()
510 # only the last version of the module is held in the cache. Note,
316 # methods for details on use.
511 # however, that we must cache the module *namespace contents* (their
317 self._user_main_modules = {}
512 # __dict__). Because if we try to cache the actual modules, old ones
513 # (uncached) could be destroyed while still holding references (such as
514 # those held by GUI objects that tend to be long-lived)>
515 #
516 # The %reset command will flush this cache. See the cache_main_mod()
517 # and clear_main_mod_cache() methods for details on use.
518
519 # This is the cache used for 'main' namespaces
520 self._main_ns_cache = {}
521 # And this is the single instance of FakeModule whose __dict__ we keep
522 # copying and clearing for reuse on each %run
523 self._user_main_module = FakeModule()
318
524
319 # A table holding all the namespaces IPython deals with, so that
525 # A table holding all the namespaces IPython deals with, so that
320 # introspection facilities can search easily.
526 # introspection facilities can search easily.
@@ -330,7 +536,7 b' class InteractiveShell(object,Magic):'
330 # a simple list.
536 # a simple list.
331 self.ns_refs_table = [ user_ns, user_global_ns, self.user_config_ns,
537 self.ns_refs_table = [ user_ns, user_global_ns, self.user_config_ns,
332 self.alias_table, self.internal_ns,
538 self.alias_table, self.internal_ns,
333 self._user_main_modules ]
539 self._main_ns_cache ]
334
540
335 # We need to insert into sys.modules something that looks like a
541 # We need to insert into sys.modules something that looks like a
336 # module but which accesses the IPython namespace, for shelve and
542 # module but which accesses the IPython namespace, for shelve and
@@ -1114,156 +1320,11 b' class InteractiveShell(object,Magic):'
1114 def user_setup(self,ipythondir,rc_suffix,mode='install'):
1320 def user_setup(self,ipythondir,rc_suffix,mode='install'):
1115 """Install the user configuration directory.
1321 """Install the user configuration directory.
1116
1322
1117 Can be called when running for the first time or to upgrade the user's
1323 Note
1118 .ipython/ directory with the mode parameter. Valid modes are 'install'
1324 ----
1119 and 'upgrade'."""
1325 DEPRECATED: use the top-level user_setup() function instead.
1120
1326 """
1121 def wait():
1327 return user_setup(ipythondir,rc_suffix,mode)
1122 try:
1123 raw_input("Please press <RETURN> to start IPython.")
1124 except EOFError:
1125 print >> Term.cout
1126 print '*'*70
1127
1128 cwd = os.getcwd() # remember where we started
1129 glb = glob.glob
1130 print '*'*70
1131 if mode == 'install':
1132 print \
1133 """Welcome to IPython. I will try to create a personal configuration directory
1134 where you can customize many aspects of IPython's functionality in:\n"""
1135 else:
1136 print 'I am going to upgrade your configuration in:'
1137
1138 print ipythondir
1139
1140 rcdirend = os.path.join('IPython','UserConfig')
1141 cfg = lambda d: os.path.join(d,rcdirend)
1142 try:
1143 rcdir = filter(os.path.isdir,map(cfg,sys.path))[0]
1144 print "Initializing from configuration",rcdir
1145 except IndexError:
1146 warning = """
1147 Installation error. IPython's directory was not found.
1148
1149 Check the following:
1150
1151 The ipython/IPython directory should be in a directory belonging to your
1152 PYTHONPATH environment variable (that is, it should be in a directory
1153 belonging to sys.path). You can copy it explicitly there or just link to it.
1154
1155 IPython will create a minimal default configuration for you.
1156
1157 """
1158 warn(warning)
1159 wait()
1160
1161 if sys.platform =='win32':
1162 inif = 'ipythonrc.ini'
1163 else:
1164 inif = 'ipythonrc'
1165 minimal_setup = {'ipy_user_conf.py' : 'import ipy_defaults',
1166 inif : '# intentionally left blank' }
1167 os.makedirs(ipythondir, mode = 0777)
1168 for f, cont in minimal_setup.items():
1169 open(ipythondir + '/' + f,'w').write(cont)
1170
1171 return
1172
1173 if mode == 'install':
1174 try:
1175 shutil.copytree(rcdir,ipythondir)
1176 os.chdir(ipythondir)
1177 rc_files = glb("ipythonrc*")
1178 for rc_file in rc_files:
1179 os.rename(rc_file,rc_file+rc_suffix)
1180 except:
1181 warning = """
1182
1183 There was a problem with the installation:
1184 %s
1185 Try to correct it or contact the developers if you think it's a bug.
1186 IPython will proceed with builtin defaults.""" % sys.exc_info()[1]
1187 warn(warning)
1188 wait()
1189 return
1190
1191 elif mode == 'upgrade':
1192 try:
1193 os.chdir(ipythondir)
1194 except:
1195 print """
1196 Can not upgrade: changing to directory %s failed. Details:
1197 %s
1198 """ % (ipythondir,sys.exc_info()[1])
1199 wait()
1200 return
1201 else:
1202 sources = glb(os.path.join(rcdir,'[A-Za-z]*'))
1203 for new_full_path in sources:
1204 new_filename = os.path.basename(new_full_path)
1205 if new_filename.startswith('ipythonrc'):
1206 new_filename = new_filename + rc_suffix
1207 # The config directory should only contain files, skip any
1208 # directories which may be there (like CVS)
1209 if os.path.isdir(new_full_path):
1210 continue
1211 if os.path.exists(new_filename):
1212 old_file = new_filename+'.old'
1213 if os.path.exists(old_file):
1214 os.remove(old_file)
1215 os.rename(new_filename,old_file)
1216 shutil.copy(new_full_path,new_filename)
1217 else:
1218 raise ValueError,'unrecognized mode for install:',`mode`
1219
1220 # Fix line-endings to those native to each platform in the config
1221 # directory.
1222 try:
1223 os.chdir(ipythondir)
1224 except:
1225 print """
1226 Problem: changing to directory %s failed.
1227 Details:
1228 %s
1229
1230 Some configuration files may have incorrect line endings. This should not
1231 cause any problems during execution. """ % (ipythondir,sys.exc_info()[1])
1232 wait()
1233 else:
1234 for fname in glb('ipythonrc*'):
1235 try:
1236 native_line_ends(fname,backup=0)
1237 except IOError:
1238 pass
1239
1240 if mode == 'install':
1241 print """
1242 Successful installation!
1243
1244 Please read the sections 'Initial Configuration' and 'Quick Tips' in the
1245 IPython manual (there are both HTML and PDF versions supplied with the
1246 distribution) to make sure that your system environment is properly configured
1247 to take advantage of IPython's features.
1248
1249 Important note: the configuration system has changed! The old system is
1250 still in place, but its setting may be partly overridden by the settings in
1251 "~/.ipython/ipy_user_conf.py" config file. Please take a look at the file
1252 if some of the new settings bother you.
1253
1254 """
1255 else:
1256 print """
1257 Successful upgrade!
1258
1259 All files in your directory:
1260 %(ipythondir)s
1261 which would have been overwritten by the upgrade were backed up with a .old
1262 extension. If you had made particular customizations in those files you may
1263 want to merge them back into the new files.""" % locals()
1264 wait()
1265 os.chdir(cwd)
1266 # end user_setup()
1267
1328
1268 def atexit_operations(self):
1329 def atexit_operations(self):
1269 """This will be executed at the time of exit.
1330 """This will be executed at the time of exit.
@@ -1417,8 +1478,9 b' want to merge them back into the new files.""" % locals()'
1417 #print "loading rl:",rlcommand # dbg
1478 #print "loading rl:",rlcommand # dbg
1418 readline.parse_and_bind(rlcommand)
1479 readline.parse_and_bind(rlcommand)
1419
1480
1420 # remove some chars from the delimiters list
1481 # Remove some chars from the delimiters list. If we encounter
1421 delims = readline.get_completer_delims()
1482 # unicode chars, discard them.
1483 delims = readline.get_completer_delims().encode("ascii", "ignore")
1422 delims = delims.translate(string._idmap,
1484 delims = delims.translate(string._idmap,
1423 self.rc.readline_remove_delims)
1485 self.rc.readline_remove_delims)
1424 readline.set_completer_delims(delims)
1486 readline.set_completer_delims(delims)
@@ -1441,35 +1503,53 b' want to merge them back into the new files.""" % locals()'
1441 return True
1503 return True
1442 return ask_yes_no(prompt,default)
1504 return ask_yes_no(prompt,default)
1443
1505
1444 def cache_main_mod(self,mod):
1506 def new_main_mod(self,ns=None):
1445 """Cache a main module.
1507 """Return a new 'main' module object for user code execution.
1508 """
1509 main_mod = self._user_main_module
1510 init_fakemod_dict(main_mod,ns)
1511 return main_mod
1512
1513 def cache_main_mod(self,ns,fname):
1514 """Cache a main module's namespace.
1446
1515
1447 When scripts are executed via %run, we must keep a reference to their
1516 When scripts are executed via %run, we must keep a reference to the
1448 __main__ module (a FakeModule instance) around so that Python doesn't
1517 namespace of their __main__ module (a FakeModule instance) around so
1449 clear it, rendering objects defined therein useless.
1518 that Python doesn't clear it, rendering objects defined therein
1519 useless.
1450
1520
1451 This method keeps said reference in a private dict, keyed by the
1521 This method keeps said reference in a private dict, keyed by the
1452 absolute path of the module object (which corresponds to the script
1522 absolute path of the module object (which corresponds to the script
1453 path). This way, for multiple executions of the same script we only
1523 path). This way, for multiple executions of the same script we only
1454 keep one copy of __main__ (the last one), thus preventing memory leaks
1524 keep one copy of the namespace (the last one), thus preventing memory
1455 from old references while allowing the objects from the last execution
1525 leaks from old references while allowing the objects from the last
1456 to be accessible.
1526 execution to be accessible.
1527
1528 Note: we can not allow the actual FakeModule instances to be deleted,
1529 because of how Python tears down modules (it hard-sets all their
1530 references to None without regard for reference counts). This method
1531 must therefore make a *copy* of the given namespace, to allow the
1532 original module's __dict__ to be cleared and reused.
1457
1533
1534
1458 Parameters
1535 Parameters
1459 ----------
1536 ----------
1460 mod : a module object
1537 ns : a namespace (a dict, typically)
1538
1539 fname : str
1540 Filename associated with the namespace.
1461
1541
1462 Examples
1542 Examples
1463 --------
1543 --------
1464
1544
1465 In [10]: import IPython
1545 In [10]: import IPython
1466
1546
1467 In [11]: _ip.IP.cache_main_mod(IPython)
1547 In [11]: _ip.IP.cache_main_mod(IPython.__dict__,IPython.__file__)
1468
1548
1469 In [12]: IPython.__file__ in _ip.IP._user_main_modules
1549 In [12]: IPython.__file__ in _ip.IP._main_ns_cache
1470 Out[12]: True
1550 Out[12]: True
1471 """
1551 """
1472 self._user_main_modules[os.path.abspath(mod.__file__) ] = mod
1552 self._main_ns_cache[os.path.abspath(fname)] = ns.copy()
1473
1553
1474 def clear_main_mod_cache(self):
1554 def clear_main_mod_cache(self):
1475 """Clear the cache of main modules.
1555 """Clear the cache of main modules.
@@ -1481,17 +1561,17 b' want to merge them back into the new files.""" % locals()'
1481
1561
1482 In [15]: import IPython
1562 In [15]: import IPython
1483
1563
1484 In [16]: _ip.IP.cache_main_mod(IPython)
1564 In [16]: _ip.IP.cache_main_mod(IPython.__dict__,IPython.__file__)
1485
1565
1486 In [17]: len(_ip.IP._user_main_modules) > 0
1566 In [17]: len(_ip.IP._main_ns_cache) > 0
1487 Out[17]: True
1567 Out[17]: True
1488
1568
1489 In [18]: _ip.IP.clear_main_mod_cache()
1569 In [18]: _ip.IP.clear_main_mod_cache()
1490
1570
1491 In [19]: len(_ip.IP._user_main_modules) == 0
1571 In [19]: len(_ip.IP._main_ns_cache) == 0
1492 Out[19]: True
1572 Out[19]: True
1493 """
1573 """
1494 self._user_main_modules.clear()
1574 self._main_ns_cache.clear()
1495
1575
1496 def _should_recompile(self,e):
1576 def _should_recompile(self,e):
1497 """Utility routine for edit_syntax_error"""
1577 """Utility routine for edit_syntax_error"""
@@ -2129,7 +2209,7 b' want to merge them back into the new files.""" % locals()'
2129
2209
2130 try:
2210 try:
2131 code = self.compile(source,filename,symbol)
2211 code = self.compile(source,filename,symbol)
2132 except (OverflowError, SyntaxError, ValueError, TypeError):
2212 except (OverflowError, SyntaxError, ValueError, TypeError, MemoryError):
2133 # Case 1
2213 # Case 1
2134 self.showsyntaxerror(filename)
2214 self.showsyntaxerror(filename)
2135 return None
2215 return None
@@ -55,9 +55,9 b' from IPython.iplib import InteractiveShell'
55 from IPython.usage import cmd_line_usage,interactive_usage
55 from IPython.usage import cmd_line_usage,interactive_usage
56 from IPython.genutils import *
56 from IPython.genutils import *
57
57
58 def force_import(modname):
58 def force_import(modname,force_reload=False):
59 if modname in sys.modules:
59 if modname in sys.modules and force_reload:
60 print "reload",modname
60 info("reloading: %s" % modname)
61 reload(sys.modules[modname])
61 reload(sys.modules[modname])
62 else:
62 else:
63 __import__(modname)
63 __import__(modname)
@@ -625,25 +625,25 b" object? -> Details about 'object'. ?object also works, ?? prints more."
625 except:
625 except:
626 IP.InteractiveTB()
626 IP.InteractiveTB()
627 import_fail_info('ipy_system_conf')
627 import_fail_info('ipy_system_conf')
628
628
629 # only import prof module if ipythonrc-PROF was not found
629 # only import prof module if ipythonrc-PROF was not found
630 if opts_all.profile and not profile_handled_by_legacy:
630 if opts_all.profile and not profile_handled_by_legacy:
631 profmodname = 'ipy_profile_' + opts_all.profile
631 profmodname = 'ipy_profile_' + opts_all.profile
632 try:
632 try:
633
634 force_import(profmodname)
633 force_import(profmodname)
635 except:
634 except:
636 IP.InteractiveTB()
635 IP.InteractiveTB()
637 print "Error importing",profmodname,"- perhaps you should run %upgrade?"
636 print "Error importing",profmodname,\
637 "- perhaps you should run %upgrade?"
638 import_fail_info(profmodname)
638 import_fail_info(profmodname)
639 else:
639 else:
640 opts.profile = opts_all.profile
640 opts.profile = opts_all.profile
641 else:
641 else:
642 force_import('ipy_profile_none')
642 force_import('ipy_profile_none')
643 # XXX - this is wrong: ipy_user_conf should not be loaded unconditionally,
644 # since the user could have specified a config file path by hand.
643 try:
645 try:
644
645 force_import('ipy_user_conf')
646 force_import('ipy_user_conf')
646
647 except:
647 except:
648 conf = opts_all.ipythondir + "/ipy_user_conf.py"
648 conf = opts_all.ipythondir + "/ipy_user_conf.py"
649 IP.InteractiveTB()
649 IP.InteractiveTB()
@@ -15,10 +15,11 b' if they need blocking clients or in `asyncclient.py` if they want asynchronous,'
15 deferred/Twisted using clients.
15 deferred/Twisted using clients.
16 """
16 """
17 __docformat__ = "restructuredtext en"
17 __docformat__ = "restructuredtext en"
18 #-------------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Copyright (C) 2008 The IPython Development Team
19 # Copyright (C) 2008 The IPython Development Team
20 #
20 #
21 # Distributed under the terms of the BSD License. The full license is in
21 # Distributed under the terms of the BSD License. The full license is in
22 # the file COPYING, distributed as part of this software.
22 # the file COPYING, distributed as part of this software.
23 #-------------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 No newline at end of file
24
25 from IPython.kernel.error import TaskRejectError No newline at end of file
@@ -15,6 +15,7 b' __docformat__ = "restructuredtext en"'
15 # Imports
15 # Imports
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17
17
18 import os, sys
18 from os.path import join as pjoin
19 from os.path import join as pjoin
19
20
20 from IPython.external.configobj import ConfigObj
21 from IPython.external.configobj import ConfigObj
@@ -23,6 +24,7 b' from IPython.genutils import get_ipython_dir, get_security_dir'
23
24
24 default_kernel_config = ConfigObj()
25 default_kernel_config = ConfigObj()
25
26
27 # This will raise OSError if ipythondir doesn't exist.
26 security_dir = get_security_dir()
28 security_dir = get_security_dir()
27
29
28 #-------------------------------------------------------------------------------
30 #-------------------------------------------------------------------------------
@@ -679,21 +679,22 b' class Interpreter(object):'
679 # to exec will fail however. There seems to be some inconsistency in
679 # to exec will fail however. There seems to be some inconsistency in
680 # how trailing whitespace is handled, but this seems to work.
680 # how trailing whitespace is handled, but this seems to work.
681 python = python.strip()
681 python = python.strip()
682
682
683 # The compiler module does not like unicode. We need to convert
683 # The compiler module does not like unicode. We need to convert
684 # it encode it:
684 # it encode it:
685 if isinstance(python, unicode):
685 if isinstance(python, unicode):
686 # Use the utf-8-sig BOM so the compiler detects this a UTF-8
686 # Use the utf-8-sig BOM so the compiler detects this a UTF-8
687 # encode string.
687 # encode string.
688 python = '\xef\xbb\xbf' + python.encode('utf-8')
688 python = '\xef\xbb\xbf' + python.encode('utf-8')
689
689
690 # The compiler module will parse the code into an abstract syntax tree.
690 # The compiler module will parse the code into an abstract syntax tree.
691 # This has a bug with str("a\nb"), but not str("""a\nb""")!!!
691 ast = compiler.parse(python)
692 ast = compiler.parse(python)
692
693
693 # Uncomment to help debug the ast tree
694 # Uncomment to help debug the ast tree
694 # for n in ast.node:
695 # for n in ast.node:
695 # print n.lineno,'->',n
696 # print n.lineno,'->',n
696
697
697 # Each separate command is available by iterating over ast.node. The
698 # Each separate command is available by iterating over ast.node. The
698 # lineno attribute is the line number (1-indexed) beginning the commands
699 # lineno attribute is the line number (1-indexed) beginning the commands
699 # suite.
700 # suite.
@@ -703,20 +704,26 b' class Interpreter(object):'
703 # We might eventually discover other cases where lineno is None and have
704 # We might eventually discover other cases where lineno is None and have
704 # to put in a more sophisticated test.
705 # to put in a more sophisticated test.
705 linenos = [x.lineno-1 for x in ast.node if x.lineno is not None]
706 linenos = [x.lineno-1 for x in ast.node if x.lineno is not None]
706
707
707 # When we finally get the slices, we will need to slice all the way to
708 # When we finally get the slices, we will need to slice all the way to
708 # the end even though we don't have a line number for it. Fortunately,
709 # the end even though we don't have a line number for it. Fortunately,
709 # None does the job nicely.
710 # None does the job nicely.
710 linenos.append(None)
711 linenos.append(None)
712
713 # Same problem at the other end: sometimes the ast tree has its
714 # first complete statement not starting on line 0. In this case
715 # we might miss part of it. This fixes ticket 266993. Thanks Gael!
716 linenos[0] = 0
717
711 lines = python.splitlines()
718 lines = python.splitlines()
712
719
713 # Create a list of atomic commands.
720 # Create a list of atomic commands.
714 cmds = []
721 cmds = []
715 for i, j in zip(linenos[:-1], linenos[1:]):
722 for i, j in zip(linenos[:-1], linenos[1:]):
716 cmd = lines[i:j]
723 cmd = lines[i:j]
717 if cmd:
724 if cmd:
718 cmds.append('\n'.join(cmd)+'\n')
725 cmds.append('\n'.join(cmd)+'\n')
719
726
720 return cmds
727 return cmds
721
728
722 def error(self, text):
729 def error(self, text):
@@ -15,6 +15,8 b' __docformat__ = "restructuredtext en"'
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # Tell nose to skip the testing of this module
19 __test__ = {}
18
20
19 class NotificationCenter(object):
21 class NotificationCenter(object):
20 """Synchronous notification center
22 """Synchronous notification center
@@ -2,25 +2,61 b''
2
2
3 """This file contains unittests for the interpreter.py module."""
3 """This file contains unittests for the interpreter.py module."""
4
4
5 __docformat__ = "restructuredtext en"
6
7 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
6 # Copyright (C) 2008-2009 The IPython Development Team
9 #
7 #
10 # Distributed under the terms of the BSD License. The full license is in
8 # Distributed under the terms of the BSD License. The full license is
11 # the file COPYING, distributed as part of this software.
9 # in the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
13
11
14 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
15 # Imports
13 # Imports
16 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
17
15
16 # Tell nose to skip this module
17 __test__ = {}
18
19 from twisted.trial import unittest
18 from IPython.kernel.core.interpreter import Interpreter
20 from IPython.kernel.core.interpreter import Interpreter
19
21
20 def test_unicode():
22 #-----------------------------------------------------------------------------
21 """ Test unicode handling with the interpreter.
23 # Tests
22 """
24 #-----------------------------------------------------------------------------
23 i = Interpreter()
25
24 i.execute_python(u'print "ù"')
26 class TestInterpreter(unittest.TestCase):
25 i.execute_python('print "ù"')
27
28 def test_unicode(self):
29 """ Test unicode handling with the interpreter."""
30 i = Interpreter()
31 i.execute_python(u'print "ù"')
32 i.execute_python('print "ù"')
33
34 def test_ticket266993(self):
35 """ Test for ticket 266993."""
36 i = Interpreter()
37 i.execute('str("""a\nb""")')
38
39 def test_ticket364347(self):
40 """Test for ticket 364347."""
41 i = Interpreter()
42 i.split_commands('str("a\\nb")')
43
44 def test_split_commands(self):
45 """ Test that commands are indeed individually split."""
46 i = Interpreter()
47 test_atoms = [('(1\n + 1)', ),
48 ('1', '1', ),
49 ]
50 for atoms in test_atoms:
51 atoms = [atom.rstrip() + '\n' for atom in atoms]
52 self.assertEquals(i.split_commands(''.join(atoms)),atoms)
53
54 def test_long_lines(self):
55 """ Test for spurious syntax error created by the interpreter."""
56 test_strings = [u'( 1 +\n 1\n )\n\n',
57 u'(1 \n + 1\n )\n\n',
58 ]
59 i = Interpreter()
60 for s in test_strings:
61 i.execute(s)
26
62
@@ -2,26 +2,26 b''
2
2
3 """This file contains unittests for the notification.py module."""
3 """This file contains unittests for the notification.py module."""
4
4
5 __docformat__ = "restructuredtext en"
6
7 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
6 # Copyright (C) 2008-2009 The IPython Development Team
9 #
7 #
10 # Distributed under the terms of the BSD License. The full license is in
8 # Distributed under the terms of the BSD License. The full license is
11 # the file COPYING, distributed as part of this software.
9 # in the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
13
11
14 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
15 # Imports
13 # Imports
16 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
17
15
18 import unittest
16 # Tell nose to skip this module
17 __test__ = {}
18
19 from twisted.trial import unittest
19 import IPython.kernel.core.notification as notification
20 import IPython.kernel.core.notification as notification
20 from nose.tools import timed
21
21
22 #
22 #-----------------------------------------------------------------------------
23 # Supporting test classes
23 # Support Classes
24 #
24 #-----------------------------------------------------------------------------
25
25
26 class Observer(object):
26 class Observer(object):
27 """docstring for Observer"""
27 """docstring for Observer"""
@@ -36,7 +36,6 b' class Observer(object):'
36 self.expectedType,
36 self.expectedType,
37 self.expectedSender)
37 self.expectedSender)
38
38
39
40 def callback(self, theType, sender, args={}):
39 def callback(self, theType, sender, args={}):
41 """callback"""
40 """callback"""
42
41
@@ -47,7 +46,6 b' class Observer(object):'
47 assert(args == self.expectedKwArgs)
46 assert(args == self.expectedKwArgs)
48 self.recieved = True
47 self.recieved = True
49
48
50
51 def verify(self):
49 def verify(self):
52 """verify"""
50 """verify"""
53
51
@@ -57,7 +55,6 b' class Observer(object):'
57 """reset"""
55 """reset"""
58
56
59 self.recieved = False
57 self.recieved = False
60
61
58
62
59
63 class Notifier(object):
60 class Notifier(object):
@@ -72,11 +69,10 b' class Notifier(object):'
72
69
73 center.post_notification(self.theType, self,
70 center.post_notification(self.theType, self,
74 **self.kwargs)
71 **self.kwargs)
75
76
72
77 #
73 #-----------------------------------------------------------------------------
78 # Test Cases
74 # Tests
79 #
75 #-----------------------------------------------------------------------------
80
76
81 class NotificationTests(unittest.TestCase):
77 class NotificationTests(unittest.TestCase):
82 """docstring for NotificationTests"""
78 """docstring for NotificationTests"""
@@ -94,7 +90,6 b' class NotificationTests(unittest.TestCase):'
94
90
95 observer.verify()
91 observer.verify()
96
92
97
98 def test_type_specificity(self):
93 def test_type_specificity(self):
99 """Test that observers are registered by type"""
94 """Test that observers are registered by type"""
100
95
@@ -109,7 +104,6 b' class NotificationTests(unittest.TestCase):'
109
104
110 observer.verify()
105 observer.verify()
111
106
112
113 def test_sender_specificity(self):
107 def test_sender_specificity(self):
114 """Test that observers are registered by sender"""
108 """Test that observers are registered by sender"""
115
109
@@ -123,7 +117,6 b' class NotificationTests(unittest.TestCase):'
123
117
124 observer.verify()
118 observer.verify()
125
119
126
127 def test_remove_all_observers(self):
120 def test_remove_all_observers(self):
128 """White-box test for remove_all_observers"""
121 """White-box test for remove_all_observers"""
129
122
@@ -136,8 +129,7 b' class NotificationTests(unittest.TestCase):'
136 notification.sharedCenter.remove_all_observers()
129 notification.sharedCenter.remove_all_observers()
137
130
138 self.assert_(len(notification.sharedCenter.observers) == 0, "observers removed")
131 self.assert_(len(notification.sharedCenter.observers) == 0, "observers removed")
139
132
140
141 def test_any_sender(self):
133 def test_any_sender(self):
142 """test_any_sender"""
134 """test_any_sender"""
143
135
@@ -153,9 +145,7 b' class NotificationTests(unittest.TestCase):'
153 observer.reset()
145 observer.reset()
154 sender2.post()
146 sender2.post()
155 observer.verify()
147 observer.verify()
156
148
157
158 @timed(.01)
159 def test_post_performance(self):
149 def test_post_performance(self):
160 """Test that post_notification, even with many registered irrelevant
150 """Test that post_notification, even with many registered irrelevant
161 observers is fast"""
151 observers is fast"""
@@ -168,4 +158,4 b' class NotificationTests(unittest.TestCase):'
168 notification.sharedCenter.post_notification('EXPECTED_TYPE', self)
158 notification.sharedCenter.post_notification('EXPECTED_TYPE', self)
169
159
170 o.verify()
160 o.verify()
171
161
@@ -2,69 +2,77 b''
2 """
2 """
3 Test the output capture at the OS level, using file descriptors.
3 Test the output capture at the OS level, using file descriptors.
4 """
4 """
5
5 #-----------------------------------------------------------------------------
6 __docformat__ = "restructuredtext en"
6 # Copyright (C) 2008-2009 The IPython Development Team
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
7 #
11 # Distributed under the terms of the BSD License. The full license is
8 # Distributed under the terms of the BSD License. The full license is
12 # in the file COPYING, distributed as part of this software.
9 # in the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
14
11
12 #-----------------------------------------------------------------------------
13 # Imports
14 #-----------------------------------------------------------------------------
15
16 # Tell nose to skip this module
17 __test__ = {}
15
18
16 # Stdlib imports
17 import os
18 from cStringIO import StringIO
19 from cStringIO import StringIO
20 import os
21
22 from twisted.trial import unittest
19
23
20 # Our own imports
24 from IPython.testing import decorators_trial as dec
21 from IPython.testing import decorators as dec
22
25
23 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
24 # Test functions
27 # Tests
28 #-----------------------------------------------------------------------------
25
29
26 @dec.skip_win32
27 def test_redirector():
28 """ Checks that the redirector can be used to do synchronous capture.
29 """
30 from IPython.kernel.core.fd_redirector import FDRedirector
31 r = FDRedirector()
32 out = StringIO()
33 try:
34 r.start()
35 for i in range(10):
36 os.system('echo %ic' % i)
37 print >>out, r.getvalue(),
38 print >>out, i
39 except:
40 r.stop()
41 raise
42 r.stop()
43 result1 = out.getvalue()
44 result2 = "".join("%ic\n%i\n" %(i, i) for i in range(10))
45 assert result1 == result2
46
30
31 class TestRedirector(unittest.TestCase):
47
32
48 @dec.skip_win32
33 @dec.skip_win32
49 def test_redirector_output_trap():
34 def test_redirector(self):
50 """ This test check not only that the redirector_output_trap does
35 """Checks that the redirector can be used to do synchronous capture.
36 """
37 from IPython.kernel.core.fd_redirector import FDRedirector
38 r = FDRedirector()
39 out = StringIO()
40 try:
41 r.start()
42 for i in range(10):
43 os.system('echo %ic' % i)
44 print >>out, r.getvalue(),
45 print >>out, i
46 except:
47 r.stop()
48 raise
49 r.stop()
50 result1 = out.getvalue()
51 result2 = "".join("%ic\n%i\n" %(i, i) for i in range(10))
52 self.assertEquals(result1, result2)
53
54 @dec.skip_win32
55 def test_redirector_output_trap(self):
56 """Check the greedy trapping behavior of the traps.
57
58 This test check not only that the redirector_output_trap does
51 trap the output, but also that it does it in a gready way, that
59 trap the output, but also that it does it in a gready way, that
52 is by calling the callback ASAP.
60 is by calling the callback ASAP.
53 """
61 """
54 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
62 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
55 out = StringIO()
63 out = StringIO()
56 trap = RedirectorOutputTrap(out.write, out.write)
64 trap = RedirectorOutputTrap(out.write, out.write)
57 try:
65 try:
58 trap.set()
66 trap.set()
59 for i in range(10):
67 for i in range(10):
60 os.system('echo %ic' % i)
68 os.system('echo %ic' % i)
61 print "%ip" % i
69 print "%ip" % i
62 print >>out, i
70 print >>out, i
63 except:
71 except:
72 trap.unset()
73 raise
64 trap.unset()
74 trap.unset()
65 raise
75 result1 = out.getvalue()
66 trap.unset()
76 result2 = "".join("%ic\n%ip\n%i\n" %(i, i, i) for i in range(10))
67 result1 = out.getvalue()
77 self.assertEquals(result1, result2)
68 result2 = "".join("%ic\n%ip\n%i\n" %(i, i, i) for i in range(10))
78
69 assert result1 == result2
70
@@ -268,6 +268,8 b' def _formatTracebackLines(lnum, index, lines, Colors, lvals=None,scheme=None):'
268 # This lets us get fully syntax-highlighted tracebacks.
268 # This lets us get fully syntax-highlighted tracebacks.
269 if scheme is None:
269 if scheme is None:
270 try:
270 try:
271 # Again, reference to a global __IPYTHON__ that doesn't exist.
272 # XXX
271 scheme = __IPYTHON__.rc.colors
273 scheme = __IPYTHON__.rc.colors
272 except:
274 except:
273 scheme = DEFAULT_SCHEME
275 scheme = DEFAULT_SCHEME
@@ -487,10 +489,14 b' class ListTB(TBTools):'
487 else:
489 else:
488 list.append('%s\n' % str(stype))
490 list.append('%s\n' % str(stype))
489
491
490 # vds:>>
492 # This is being commented out for now as the __IPYTHON__ variable
491 if have_filedata:
493 # referenced here is not resolved and causes massive test failures
492 __IPYTHON__.hooks.synchronize_with_editor(filename, lineno, 0)
494 # and errors. B. Granger, 04/2009. XXX
493 # vds:<<
495 # See https://bugs.launchpad.net/bugs/362137
496 # # vds:>>
497 # if have_filedata:
498 # __IPYTHON__.hooks.synchronize_with_editor(filename, lineno, 0)
499 # # vds:<<
494
500
495 return list
501 return list
496
502
@@ -804,13 +810,17 b' class VerboseTB(TBTools):'
804 value = text_repr(getattr(evalue, name))
810 value = text_repr(getattr(evalue, name))
805 exception.append('\n%s%s = %s' % (indent, name, value))
811 exception.append('\n%s%s = %s' % (indent, name, value))
806
812
807 # vds: >>
813 # This is being commented out for now as the __IPYTHON__ variable
808 if records:
814 # referenced here is not resolved and causes massive test failures
809 filepath, lnum = records[-1][1:3]
815 # and errors. B. Granger, 04/2009. XXX
810 #print "file:", str(file), "linenb", str(lnum) # dbg
816 # See https://bugs.launchpad.net/bugs/362137
811 filepath = os.path.abspath(filepath)
817 # # vds: >>
812 __IPYTHON__.hooks.synchronize_with_editor(filepath, lnum, 0)
818 # if records:
813 # vds: <<
819 # filepath, lnum = records[-1][1:3]
820 # #print "file:", str(file), "linenb", str(lnum) # dbg
821 # filepath = os.path.abspath(filepath)
822 # __IPYTHON__.hooks.synchronize_with_editor(filepath, lnum, 0)
823 # # vds: <<
814
824
815 # return all our info assembled as a single string
825 # return all our info assembled as a single string
816 return '%s\n\n%s\n%s' % (head,'\n'.join(frames),''.join(exception[0]) )
826 return '%s\n\n%s\n%s' % (head,'\n'.join(frames),''.join(exception[0]) )
@@ -67,10 +67,10 b' class EngineConnector(object):'
67 self.furl = find_furl(furl_or_file)
67 self.furl = find_furl(furl_or_file)
68 except ValueError:
68 except ValueError:
69 return defer.fail(failure.Failure())
69 return defer.fail(failure.Failure())
70 # return defer.fail(failure.Failure(ValueError('not a valid furl or furl file: %r' % furl_or_file)))
70 else:
71 d = self.tub.getReference(self.furl)
71 d = self.tub.getReference(self.furl)
72 d.addCallbacks(self._register, self._log_failure)
72 d.addCallbacks(self._register, self._log_failure)
73 return d
73 return d
74
74
75 def _log_failure(self, reason):
75 def _log_failure(self, reason):
76 log.err('EngineConnector: engine registration failed:')
76 log.err('EngineConnector: engine registration failed:')
@@ -34,6 +34,9 b' __docformat__ = "restructuredtext en"'
34 # Imports
34 # Imports
35 #-------------------------------------------------------------------------------
35 #-------------------------------------------------------------------------------
36
36
37 # Tell nose to skip the testing of this module
38 __test__ = {}
39
37 import os, sys, copy
40 import os, sys, copy
38 import cPickle as pickle
41 import cPickle as pickle
39 from new import instancemethod
42 from new import instancemethod
@@ -266,8 +269,8 b' class StrictDict(dict):'
266 pickle.dumps(key, 2)
269 pickle.dumps(key, 2)
267 pickle.dumps(value, 2)
270 pickle.dumps(value, 2)
268 newvalue = copy.deepcopy(value)
271 newvalue = copy.deepcopy(value)
269 except:
272 except Exception, e:
270 raise error.InvalidProperty(value)
273 raise error.InvalidProperty("can't be a value: %r" % value)
271 dict.__setitem__(self, key, newvalue)
274 dict.__setitem__(self, key, newvalue)
272 self.modified = True
275 self.modified = True
273
276
@@ -104,6 +104,23 b' class StopLocalExecution(KernelError):'
104 class SecurityError(KernelError):
104 class SecurityError(KernelError):
105 pass
105 pass
106
106
107 class FileTimeoutError(KernelError):
108 pass
109
110 class TaskRejectError(KernelError):
111 """Exception to raise when a task should be rejected by an engine.
112
113 This exception can be used to allow a task running on an engine to test
114 if the engine (or the user's namespace on the engine) has the needed
115 task dependencies. If not, the task should raise this exception. For
116 the task to be retried on another engine, the task should be created
117 with the `retries` argument > 1.
118
119 The advantage of this approach over our older properties system is that
120 tasks have full access to the user's namespace on the engines and the
121 properties don't have to be managed or tested by the controller.
122 """
123
107 class CompositeError(KernelError):
124 class CompositeError(KernelError):
108 def __init__(self, message, elist):
125 def __init__(self, message, elist):
109 Exception.__init__(self, *(message, elist))
126 Exception.__init__(self, *(message, elist))
@@ -20,6 +20,7 b' import sys'
20 import cPickle as pickle
20 import cPickle as pickle
21 from types import FunctionType
21 from types import FunctionType
22 import linecache
22 import linecache
23 import warnings
23
24
24 from twisted.internet import reactor
25 from twisted.internet import reactor
25 from twisted.python import components, log
26 from twisted.python import components, log
@@ -389,6 +390,14 b' def strip_whitespace(source):'
389 #-------------------------------------------------------------------------------
390 #-------------------------------------------------------------------------------
390
391
391
392
393 _prop_warn = """\
394
395 We are currently refactoring the task dependency system. This might
396 involve the removal of this method and other methods related to engine
397 properties. Please see the docstrings for IPython.kernel.TaskRejectError
398 for more information."""
399
400
392 class IFullBlockingMultiEngineClient(Interface):
401 class IFullBlockingMultiEngineClient(Interface):
393 pass
402 pass
394
403
@@ -730,22 +739,27 b' class FullBlockingMultiEngineClient(InteractiveMultiEngineClient):'
730 return self._blockFromThread(self.smultiengine.queue_status, targets=targets, block=block)
739 return self._blockFromThread(self.smultiengine.queue_status, targets=targets, block=block)
731
740
732 def set_properties(self, properties, targets=None, block=None):
741 def set_properties(self, properties, targets=None, block=None):
742 warnings.warn(_prop_warn)
733 targets, block = self._findTargetsAndBlock(targets, block)
743 targets, block = self._findTargetsAndBlock(targets, block)
734 return self._blockFromThread(self.smultiengine.set_properties, properties, targets=targets, block=block)
744 return self._blockFromThread(self.smultiengine.set_properties, properties, targets=targets, block=block)
735
745
736 def get_properties(self, keys=None, targets=None, block=None):
746 def get_properties(self, keys=None, targets=None, block=None):
747 warnings.warn(_prop_warn)
737 targets, block = self._findTargetsAndBlock(targets, block)
748 targets, block = self._findTargetsAndBlock(targets, block)
738 return self._blockFromThread(self.smultiengine.get_properties, keys, targets=targets, block=block)
749 return self._blockFromThread(self.smultiengine.get_properties, keys, targets=targets, block=block)
739
750
740 def has_properties(self, keys, targets=None, block=None):
751 def has_properties(self, keys, targets=None, block=None):
752 warnings.warn(_prop_warn)
741 targets, block = self._findTargetsAndBlock(targets, block)
753 targets, block = self._findTargetsAndBlock(targets, block)
742 return self._blockFromThread(self.smultiengine.has_properties, keys, targets=targets, block=block)
754 return self._blockFromThread(self.smultiengine.has_properties, keys, targets=targets, block=block)
743
755
744 def del_properties(self, keys, targets=None, block=None):
756 def del_properties(self, keys, targets=None, block=None):
757 warnings.warn(_prop_warn)
745 targets, block = self._findTargetsAndBlock(targets, block)
758 targets, block = self._findTargetsAndBlock(targets, block)
746 return self._blockFromThread(self.smultiengine.del_properties, keys, targets=targets, block=block)
759 return self._blockFromThread(self.smultiengine.del_properties, keys, targets=targets, block=block)
747
760
748 def clear_properties(self, targets=None, block=None):
761 def clear_properties(self, targets=None, block=None):
762 warnings.warn(_prop_warn)
749 targets, block = self._findTargetsAndBlock(targets, block)
763 targets, block = self._findTargetsAndBlock(targets, block)
750 return self._blockFromThread(self.smultiengine.clear_properties, targets=targets, block=block)
764 return self._blockFromThread(self.smultiengine.clear_properties, targets=targets, block=block)
751
765
@@ -1,4 +1,4 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3
3
4 """Start an IPython cluster = (controller + engines)."""
4 """Start an IPython cluster = (controller + engines)."""
@@ -29,29 +29,35 b' from twisted.python import failure, log'
29
29
30 from IPython.external import argparse
30 from IPython.external import argparse
31 from IPython.external import Itpl
31 from IPython.external import Itpl
32 from IPython.genutils import get_ipython_dir, num_cpus
32 from IPython.genutils import (
33 get_ipython_dir,
34 get_log_dir,
35 get_security_dir,
36 num_cpus
37 )
33 from IPython.kernel.fcutil import have_crypto
38 from IPython.kernel.fcutil import have_crypto
34 from IPython.kernel.error import SecurityError
39
40 # Create various ipython directories if they don't exist.
41 # This must be done before IPython.kernel.config is imported.
42 from IPython.iplib import user_setup
43 if os.name == 'posix':
44 rc_suffix = ''
45 else:
46 rc_suffix = '.ini'
47 user_setup(get_ipython_dir(), rc_suffix, mode='install', interactive=False)
48 get_log_dir()
49 get_security_dir()
50
51 from IPython.kernel.config import config_manager as kernel_config_manager
52 from IPython.kernel.error import SecurityError, FileTimeoutError
35 from IPython.kernel.fcutil import have_crypto
53 from IPython.kernel.fcutil import have_crypto
36 from IPython.kernel.twistedutil import gatherBoth
54 from IPython.kernel.twistedutil import gatherBoth, wait_for_file
37 from IPython.kernel.util import printer
55 from IPython.kernel.util import printer
38
56
39
40 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
41 # General process handling code
58 # General process handling code
42 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
43
60
44 def find_exe(cmd):
45 try:
46 import win32api
47 except ImportError:
48 raise ImportError('you need to have pywin32 installed for this to work')
49 else:
50 try:
51 (path, offest) = win32api.SearchPath(os.environ['PATH'],cmd + '.exe')
52 except:
53 (path, offset) = win32api.SearchPath(os.environ['PATH'],cmd + '.bat')
54 return path
55
61
56 class ProcessStateError(Exception):
62 class ProcessStateError(Exception):
57 pass
63 pass
@@ -184,8 +190,10 b' class ControllerLauncher(ProcessLauncher):'
184 from IPython.kernel.scripts import ipcontroller
190 from IPython.kernel.scripts import ipcontroller
185 script_location = ipcontroller.__file__.replace('.pyc', '.py')
191 script_location = ipcontroller.__file__.replace('.pyc', '.py')
186 # The -u option here turns on unbuffered output, which is required
192 # The -u option here turns on unbuffered output, which is required
187 # on Win32 to prevent wierd conflict and problems with Twisted
193 # on Win32 to prevent wierd conflict and problems with Twisted.
188 args = [find_exe('python'), '-u', script_location]
194 # Also, use sys.executable to make sure we are picking up the
195 # right python exe.
196 args = [sys.executable, '-u', script_location]
189 else:
197 else:
190 args = ['ipcontroller']
198 args = ['ipcontroller']
191 self.extra_args = extra_args
199 self.extra_args = extra_args
@@ -204,8 +212,10 b' class EngineLauncher(ProcessLauncher):'
204 from IPython.kernel.scripts import ipengine
212 from IPython.kernel.scripts import ipengine
205 script_location = ipengine.__file__.replace('.pyc', '.py')
213 script_location = ipengine.__file__.replace('.pyc', '.py')
206 # The -u option here turns on unbuffered output, which is required
214 # The -u option here turns on unbuffered output, which is required
207 # on Win32 to prevent wierd conflict and problems with Twisted
215 # on Win32 to prevent wierd conflict and problems with Twisted.
208 args = [find_exe('python'), '-u', script_location]
216 # Also, use sys.executable to make sure we are picking up the
217 # right python exe.
218 args = [sys.executable, '-u', script_location]
209 else:
219 else:
210 args = ['ipengine']
220 args = ['ipengine']
211 self.extra_args = extra_args
221 self.extra_args = extra_args
@@ -465,7 +475,9 b' class SSHEngineSet(object):'
465 # The main functions should then just parse the command line arguments, create
475 # The main functions should then just parse the command line arguments, create
466 # the appropriate class and call a 'start' method.
476 # the appropriate class and call a 'start' method.
467
477
478
468 def check_security(args, cont_args):
479 def check_security(args, cont_args):
480 """Check to see if we should run with SSL support."""
469 if (not args.x or not args.y) and not have_crypto:
481 if (not args.x or not args.y) and not have_crypto:
470 log.err("""
482 log.err("""
471 OpenSSL/pyOpenSSL is not available, so we can't run in secure mode.
483 OpenSSL/pyOpenSSL is not available, so we can't run in secure mode.
@@ -478,7 +490,9 b' Try running ipcluster with the -xy flags: ipcluster local -xy -n 4""")'
478 cont_args.append('-y')
490 cont_args.append('-y')
479 return True
491 return True
480
492
493
481 def check_reuse(args, cont_args):
494 def check_reuse(args, cont_args):
495 """Check to see if we should try to resuse FURL files."""
482 if args.r:
496 if args.r:
483 cont_args.append('-r')
497 cont_args.append('-r')
484 if args.client_port == 0 or args.engine_port == 0:
498 if args.client_port == 0 or args.engine_port == 0:
@@ -491,6 +505,25 b' the --client-port and --engine-port options.""")'
491 cont_args.append('--engine-port=%i' % args.engine_port)
505 cont_args.append('--engine-port=%i' % args.engine_port)
492 return True
506 return True
493
507
508
509 def _err_and_stop(f):
510 """Errback to log a failure and halt the reactor on a fatal error."""
511 log.err(f)
512 reactor.stop()
513
514
515 def _delay_start(cont_pid, start_engines, furl_file, reuse):
516 """Wait for controller to create FURL files and the start the engines."""
517 if not reuse:
518 if os.path.isfile(furl_file):
519 os.unlink(furl_file)
520 log.msg('Waiting for controller to finish starting...')
521 d = wait_for_file(furl_file, delay=0.2, max_tries=50)
522 d.addCallback(lambda _: log.msg('Controller started'))
523 d.addCallback(lambda _: start_engines(cont_pid))
524 return d
525
526
494 def main_local(args):
527 def main_local(args):
495 cont_args = []
528 cont_args = []
496 cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller'))
529 cont_args.append('--logfile=%s' % pjoin(args.logdir,'ipcontroller'))
@@ -520,13 +553,10 b' def main_local(args):'
520 signal.signal(signal.SIGINT,shutdown)
553 signal.signal(signal.SIGINT,shutdown)
521 d = eset.start(args.n)
554 d = eset.start(args.n)
522 return d
555 return d
523 def delay_start(cont_pid):
556 config = kernel_config_manager.get_config_obj()
524 # This is needed because the controller doesn't start listening
557 furl_file = config['controller']['engine_furl_file']
525 # right when it starts and the controller needs to write
558 dstart.addCallback(_delay_start, start_engines, furl_file, args.r)
526 # furl files for the engine to pick up
559 dstart.addErrback(_err_and_stop)
527 reactor.callLater(1.0, start_engines, cont_pid)
528 dstart.addCallback(delay_start)
529 dstart.addErrback(lambda f: f.raiseException())
530
560
531
561
532 def main_mpi(args):
562 def main_mpi(args):
@@ -562,13 +592,10 b' def main_mpi(args):'
562 signal.signal(signal.SIGINT,shutdown)
592 signal.signal(signal.SIGINT,shutdown)
563 d = eset.start()
593 d = eset.start()
564 return d
594 return d
565 def delay_start(cont_pid):
595 config = kernel_config_manager.get_config_obj()
566 # This is needed because the controller doesn't start listening
596 furl_file = config['controller']['engine_furl_file']
567 # right when it starts and the controller needs to write
597 dstart.addCallback(_delay_start, start_engines, furl_file, args.r)
568 # furl files for the engine to pick up
598 dstart.addErrback(_err_and_stop)
569 reactor.callLater(1.0, start_engines, cont_pid)
570 dstart.addCallback(delay_start)
571 dstart.addErrback(lambda f: f.raiseException())
572
599
573
600
574 def main_pbs(args):
601 def main_pbs(args):
@@ -595,8 +622,10 b' def main_pbs(args):'
595 signal.signal(signal.SIGINT,shutdown)
622 signal.signal(signal.SIGINT,shutdown)
596 d = pbs_set.start(args.n)
623 d = pbs_set.start(args.n)
597 return d
624 return d
598 dstart.addCallback(start_engines)
625 config = kernel_config_manager.get_config_obj()
599 dstart.addErrback(lambda f: f.raiseException())
626 furl_file = config['controller']['engine_furl_file']
627 dstart.addCallback(_delay_start, start_engines, furl_file, args.r)
628 dstart.addErrback(_err_and_stop)
600
629
601
630
602 def main_ssh(args):
631 def main_ssh(args):
@@ -637,12 +666,10 b' def main_ssh(args):'
637 signal.signal(signal.SIGINT,shutdown)
666 signal.signal(signal.SIGINT,shutdown)
638 d = ssh_set.start(clusterfile['send_furl'])
667 d = ssh_set.start(clusterfile['send_furl'])
639 return d
668 return d
640
669 config = kernel_config_manager.get_config_obj()
641 def delay_start(cont_pid):
670 furl_file = config['controller']['engine_furl_file']
642 reactor.callLater(1.0, start_engines, cont_pid)
671 dstart.addCallback(_delay_start, start_engines, furl_file, args.r)
643
672 dstart.addErrback(_err_and_stop)
644 dstart.addCallback(delay_start)
645 dstart.addErrback(lambda f: f.raiseException())
646
673
647
674
648 def get_args():
675 def get_args():
@@ -697,8 +724,11 b' def get_args():'
697
724
698 parser = argparse.ArgumentParser(
725 parser = argparse.ArgumentParser(
699 description='IPython cluster startup. This starts a controller and\
726 description='IPython cluster startup. This starts a controller and\
700 engines using various approaches. THIS IS A TECHNOLOGY PREVIEW AND\
727 engines using various approaches. Use the IPYTHONDIR environment\
701 THE API WILL CHANGE SIGNIFICANTLY BEFORE THE FINAL RELEASE.'
728 variable to change your IPython directory from the default of\
729 .ipython or _ipython. The log and security subdirectories of your\
730 IPython directory will be used by this script for log files and\
731 security files.'
702 )
732 )
703 subparsers = parser.add_subparsers(
733 subparsers = parser.add_subparsers(
704 help='available cluster types. For help, do "ipcluster TYPE --help"')
734 help='available cluster types. For help, do "ipcluster TYPE --help"')
@@ -21,8 +21,10 b' __docformat__ = "restructuredtext en"'
21 import sys
21 import sys
22 sys.path.insert(0, '')
22 sys.path.insert(0, '')
23
23
24 import sys, time, os
25 from optparse import OptionParser
24 from optparse import OptionParser
25 import os
26 import time
27 import tempfile
26
28
27 from twisted.application import internet, service
29 from twisted.application import internet, service
28 from twisted.internet import reactor, error, defer
30 from twisted.internet import reactor, error, defer
@@ -37,6 +39,18 b' from IPython.kernel.error import SecurityError'
37 from IPython.kernel import controllerservice
39 from IPython.kernel import controllerservice
38 from IPython.kernel.fcutil import check_furl_file_security
40 from IPython.kernel.fcutil import check_furl_file_security
39
41
42 # Create various ipython directories if they don't exist.
43 # This must be done before IPython.kernel.config is imported.
44 from IPython.iplib import user_setup
45 from IPython.genutils import get_ipython_dir, get_log_dir, get_security_dir
46 if os.name == 'posix':
47 rc_suffix = ''
48 else:
49 rc_suffix = '.ini'
50 user_setup(get_ipython_dir(), rc_suffix, mode='install', interactive=False)
51 get_log_dir()
52 get_security_dir()
53
40 from IPython.kernel.config import config_manager as kernel_config_manager
54 from IPython.kernel.config import config_manager as kernel_config_manager
41 from IPython.config.cutils import import_item
55 from IPython.config.cutils import import_item
42
56
@@ -45,6 +59,10 b' from IPython.config.cutils import import_item'
45 # Code
59 # Code
46 #-------------------------------------------------------------------------------
60 #-------------------------------------------------------------------------------
47
61
62 def get_temp_furlfile(filename):
63 return tempfile.mktemp(dir=os.path.dirname(filename),
64 prefix=os.path.basename(filename))
65
48 def make_tub(ip, port, secure, cert_file):
66 def make_tub(ip, port, secure, cert_file):
49 """
67 """
50 Create a listening tub given an ip, port, and cert_file location.
68 Create a listening tub given an ip, port, and cert_file location.
@@ -107,13 +125,18 b' def make_client_service(controller_service, config):'
107 """Set the location for the tub and return a deferred."""
125 """Set the location for the tub and return a deferred."""
108
126
109 def register(empty, ref, furl_file):
127 def register(empty, ref, furl_file):
110 client_tub.registerReference(ref, furlFile=furl_file)
128 # We create and then move to make sure that when the file
129 # appears to other processes, the buffer has the flushed
130 # and the file has been closed
131 temp_furl_file = get_temp_furlfile(furl_file)
132 client_tub.registerReference(ref, furlFile=temp_furl_file)
133 os.rename(temp_furl_file, furl_file)
111
134
112 if location == '':
135 if location == '':
113 d = client_tub.setLocationAutomatically()
136 d = client_tub.setLocationAutomatically()
114 else:
137 else:
115 d = defer.maybeDeferred(client_tub.setLocation, "%s:%i" % (location, client_listener.getPortnum()))
138 d = defer.maybeDeferred(client_tub.setLocation, "%s:%i" % (location, client_listener.getPortnum()))
116
139
117 for ciname, ci in config['controller']['controller_interfaces'].iteritems():
140 for ciname, ci in config['controller']['controller_interfaces'].iteritems():
118 log.msg("Adapting Controller to interface: %s" % ciname)
141 log.msg("Adapting Controller to interface: %s" % ciname)
119 furl_file = ci['furl_file']
142 furl_file = ci['furl_file']
@@ -154,7 +177,12 b' def make_engine_service(controller_service, config):'
154 """Set the location for the tub and return a deferred."""
177 """Set the location for the tub and return a deferred."""
155
178
156 def register(empty, ref, furl_file):
179 def register(empty, ref, furl_file):
157 engine_tub.registerReference(ref, furlFile=furl_file)
180 # We create and then move to make sure that when the file
181 # appears to other processes, the buffer has the flushed
182 # and the file has been closed
183 temp_furl_file = get_temp_furlfile(furl_file)
184 engine_tub.registerReference(ref, furlFile=temp_furl_file)
185 os.rename(temp_furl_file, furl_file)
158
186
159 if location == '':
187 if location == '':
160 d = engine_tub.setLocationAutomatically()
188 d = engine_tub.setLocationAutomatically()
@@ -236,7 +264,14 b' def init_config():'
236 Initialize the configuration using default and command line options.
264 Initialize the configuration using default and command line options.
237 """
265 """
238
266
239 parser = OptionParser()
267 parser = OptionParser("""ipcontroller [options]
268
269 Start an IPython controller.
270
271 Use the IPYTHONDIR environment variable to change your IPython directory
272 from the default of .ipython or _ipython. The log and security
273 subdirectories of your IPython directory will be used by this script
274 for log files and security files.""")
240
275
241 # Client related options
276 # Client related options
242 parser.add_option(
277 parser.add_option(
@@ -325,12 +360,6 b' def init_config():'
325 help="log file name (default is stdout)"
360 help="log file name (default is stdout)"
326 )
361 )
327 parser.add_option(
362 parser.add_option(
328 "--ipythondir",
329 type="string",
330 dest="ipythondir",
331 help="look for config files and profiles in this directory"
332 )
333 parser.add_option(
334 "-r",
363 "-r",
335 action="store_true",
364 action="store_true",
336 dest="reuse_furls",
365 dest="reuse_furls",
@@ -339,7 +368,6 b' def init_config():'
339
368
340 (options, args) = parser.parse_args()
369 (options, args) = parser.parse_args()
341
370
342 kernel_config_manager.update_config_obj_from_default_file(options.ipythondir)
343 config = kernel_config_manager.get_config_obj()
371 config = kernel_config_manager.get_config_obj()
344
372
345 # Update with command line options
373 # Update with command line options
@@ -21,8 +21,8 b' __docformat__ = "restructuredtext en"'
21 import sys
21 import sys
22 sys.path.insert(0, '')
22 sys.path.insert(0, '')
23
23
24 import sys, os
25 from optparse import OptionParser
24 from optparse import OptionParser
25 import os
26
26
27 from twisted.application import service
27 from twisted.application import service
28 from twisted.internet import reactor
28 from twisted.internet import reactor
@@ -33,6 +33,19 b' from IPython.kernel.fcutil import Tub, UnauthenticatedTub'
33 from IPython.kernel.core.config import config_manager as core_config_manager
33 from IPython.kernel.core.config import config_manager as core_config_manager
34 from IPython.config.cutils import import_item
34 from IPython.config.cutils import import_item
35 from IPython.kernel.engineservice import EngineService
35 from IPython.kernel.engineservice import EngineService
36
37 # Create various ipython directories if they don't exist.
38 # This must be done before IPython.kernel.config is imported.
39 from IPython.iplib import user_setup
40 from IPython.genutils import get_ipython_dir, get_log_dir, get_security_dir
41 if os.name == 'posix':
42 rc_suffix = ''
43 else:
44 rc_suffix = '.ini'
45 user_setup(get_ipython_dir(), rc_suffix, mode='install', interactive=False)
46 get_log_dir()
47 get_security_dir()
48
36 from IPython.kernel.config import config_manager as kernel_config_manager
49 from IPython.kernel.config import config_manager as kernel_config_manager
37 from IPython.kernel.engineconnector import EngineConnector
50 from IPython.kernel.engineconnector import EngineConnector
38
51
@@ -106,13 +119,19 b' def start_engine():'
106 engine_connector = EngineConnector(tub_service)
119 engine_connector = EngineConnector(tub_service)
107 furl_file = kernel_config['engine']['furl_file']
120 furl_file = kernel_config['engine']['furl_file']
108 log.msg("Using furl file: %s" % furl_file)
121 log.msg("Using furl file: %s" % furl_file)
109 d = engine_connector.connect_to_controller(engine_service, furl_file)
110 def handle_error(f):
111 log.err(f)
112 if reactor.running:
113 reactor.stop()
114 d.addErrback(handle_error)
115
122
123 def call_connect(engine_service, furl_file):
124 d = engine_connector.connect_to_controller(engine_service, furl_file)
125 def handle_error(f):
126 # If this print statement is replaced by a log.err(f) I get
127 # an unhandled error, which makes no sense. I shouldn't have
128 # to use a print statement here. My only thought is that
129 # at the beginning of the process the logging is still starting up
130 print "error connecting to controller:", f.getErrorMessage()
131 reactor.callLater(0.1, reactor.stop)
132 d.addErrback(handle_error)
133
134 reactor.callWhenRunning(call_connect, engine_service, furl_file)
116 reactor.run()
135 reactor.run()
117
136
118
137
@@ -121,7 +140,14 b' def init_config():'
121 Initialize the configuration using default and command line options.
140 Initialize the configuration using default and command line options.
122 """
141 """
123
142
124 parser = OptionParser()
143 parser = OptionParser("""ipengine [options]
144
145 Start an IPython engine.
146
147 Use the IPYTHONDIR environment variable to change your IPython directory
148 from the default of .ipython or _ipython. The log and security
149 subdirectories of your IPython directory will be used by this script
150 for log files and security files.""")
125
151
126 parser.add_option(
152 parser.add_option(
127 "--furl-file",
153 "--furl-file",
@@ -142,18 +168,9 b' def init_config():'
142 dest="logfile",
168 dest="logfile",
143 help="log file name (default is stdout)"
169 help="log file name (default is stdout)"
144 )
170 )
145 parser.add_option(
146 "--ipythondir",
147 type="string",
148 dest="ipythondir",
149 help="look for config files and profiles in this directory"
150 )
151
171
152 (options, args) = parser.parse_args()
172 (options, args) = parser.parse_args()
153
173
154 kernel_config_manager.update_config_obj_from_default_file(options.ipythondir)
155 core_config_manager.update_config_obj_from_default_file(options.ipythondir)
156
157 kernel_config = kernel_config_manager.get_config_obj()
174 kernel_config = kernel_config_manager.get_config_obj()
158 # Now override with command line options
175 # Now override with command line options
159 if options.furl_file is not None:
176 if options.furl_file is not None:
@@ -16,6 +16,9 b' __docformat__ = "restructuredtext en"'
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 # Tell nose to skip the testing of this module
20 __test__ = {}
21
19 import copy, time
22 import copy, time
20 from types import FunctionType
23 from types import FunctionType
21
24
@@ -49,7 +49,7 b' class BlockingTaskClient(object):'
49 """
49 """
50
50
51 implements(
51 implements(
52 IBlockingTaskClient,
52 IBlockingTaskClient,
53 ITaskMapperFactory,
53 ITaskMapperFactory,
54 IMapper,
54 IMapper,
55 ITaskParallelDecorator
55 ITaskParallelDecorator
@@ -62,7 +62,7 b' class BlockingTaskClient(object):'
62 def run(self, task, block=False):
62 def run(self, task, block=False):
63 """Run a task on the `TaskController`.
63 """Run a task on the `TaskController`.
64
64
65 See the documentation of the `MapTask` and `StringTask` classes for
65 See the documentation of the `MapTask` and `StringTask` classes for
66 details on how to build a task of different types.
66 details on how to build a task of different types.
67
67
68 :Parameters:
68 :Parameters:
@@ -363,7 +363,8 b' class IEnginePropertiesTestCase(object):'
363 p = get_engine(%s).properties"""%self.engine.id
363 p = get_engine(%s).properties"""%self.engine.id
364 d = self.engine.execute(s)
364 d = self.engine.execute(s)
365 d.addCallback(lambda r: self.engine.execute("p['a'] = lambda _:None"))
365 d.addCallback(lambda r: self.engine.execute("p['a'] = lambda _:None"))
366 d = self.assertDeferredRaises(d, error.InvalidProperty)
366 d.addErrback(lambda f: self.assertRaises(error.InvalidProperty,
367 f.raiseException))
367 d.addCallback(lambda r: self.engine.execute("p['a'] = range(5)"))
368 d.addCallback(lambda r: self.engine.execute("p['a'] = range(5)"))
368 d.addCallback(lambda r: self.engine.execute("p['a'].append(5)"))
369 d.addCallback(lambda r: self.engine.execute("p['a'].append(5)"))
369 d.addCallback(lambda r: self.engine.get_properties('a'))
370 d.addCallback(lambda r: self.engine.get_properties('a'))
@@ -1,3 +1,6 b''
1 # Tell nose to skip this module
2 __test__ = {}
3
1 #from __future__ import with_statement
4 #from __future__ import with_statement
2
5
3 # XXX This file is currently disabled to preserve 2.4 compatibility.
6 # XXX This file is currently disabled to preserve 2.4 compatibility.
@@ -23,15 +23,15 b' __docformat__ = "restructuredtext en"'
23 # Imports
23 # Imports
24 #-------------------------------------------------------------------------------
24 #-------------------------------------------------------------------------------
25
25
26 try:
26 # Tell nose to skip this module
27 from twisted.application.service import IService
27 __test__ = {}
28 from IPython.kernel.controllerservice import ControllerService
28
29 from IPython.kernel.tests import multienginetest as met
29 from twisted.application.service import IService
30 from controllertest import IControllerCoreTestCase
30 from IPython.kernel.controllerservice import ControllerService
31 from IPython.testing.util import DeferredTestCase
31 from IPython.kernel.tests import multienginetest as met
32 except ImportError:
32 from controllertest import IControllerCoreTestCase
33 import nose
33 from IPython.testing.util import DeferredTestCase
34 raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap")
34
35
35
36 class BasicControllerServiceTest(DeferredTestCase,
36 class BasicControllerServiceTest(DeferredTestCase,
37 IControllerCoreTestCase):
37 IControllerCoreTestCase):
@@ -15,30 +15,29 b' __docformat__ = "restructuredtext en"'
15 # Imports
15 # Imports
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17
17
18 try:
18 # Tell nose to skip this module
19 from twisted.python import components
19 __test__ = {}
20 from twisted.internet import reactor, defer
21 from twisted.spread import pb
22 from twisted.internet.base import DelayedCall
23 DelayedCall.debug = True
24
20
25 import zope.interface as zi
21 from twisted.python import components
22 from twisted.internet import reactor, defer
23 from twisted.spread import pb
24 from twisted.internet.base import DelayedCall
25 DelayedCall.debug = True
26
26
27 from IPython.kernel.fcutil import Tub, UnauthenticatedTub
27 import zope.interface as zi
28 from IPython.kernel import engineservice as es
28
29 from IPython.testing.util import DeferredTestCase
29 from IPython.kernel.fcutil import Tub, UnauthenticatedTub
30 from IPython.kernel.controllerservice import IControllerBase
30 from IPython.kernel import engineservice as es
31 from IPython.kernel.enginefc import FCRemoteEngineRefFromService, IEngineBase
31 from IPython.testing.util import DeferredTestCase
32 from IPython.kernel.engineservice import IEngineQueued
32 from IPython.kernel.controllerservice import IControllerBase
33 from IPython.kernel.engineconnector import EngineConnector
33 from IPython.kernel.enginefc import FCRemoteEngineRefFromService, IEngineBase
34
34 from IPython.kernel.engineservice import IEngineQueued
35 from IPython.kernel.tests.engineservicetest import \
35 from IPython.kernel.engineconnector import EngineConnector
36 IEngineCoreTestCase, \
36
37 IEngineSerializedTestCase, \
37 from IPython.kernel.tests.engineservicetest import \
38 IEngineQueuedTestCase
38 IEngineCoreTestCase, \
39 except ImportError:
39 IEngineSerializedTestCase, \
40 import nose
40 IEngineQueuedTestCase
41 raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap")
42
41
43
42
44 class EngineFCTest(DeferredTestCase,
43 class EngineFCTest(DeferredTestCase,
@@ -23,20 +23,19 b' __docformat__ = "restructuredtext en"'
23 # Imports
23 # Imports
24 #-------------------------------------------------------------------------------
24 #-------------------------------------------------------------------------------
25
25
26 try:
26 # Tell nose to skip this module
27 from twisted.internet import defer
27 __test__ = {}
28 from twisted.application.service import IService
28
29
29 from twisted.internet import defer
30 from IPython.kernel import engineservice as es
30 from twisted.application.service import IService
31 from IPython.testing.util import DeferredTestCase
31
32 from IPython.kernel.tests.engineservicetest import \
32 from IPython.kernel import engineservice as es
33 IEngineCoreTestCase, \
33 from IPython.testing.util import DeferredTestCase
34 IEngineSerializedTestCase, \
34 from IPython.kernel.tests.engineservicetest import \
35 IEngineQueuedTestCase, \
35 IEngineCoreTestCase, \
36 IEnginePropertiesTestCase
36 IEngineSerializedTestCase, \
37 except ImportError:
37 IEngineQueuedTestCase, \
38 import nose
38 IEnginePropertiesTestCase
39 raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap")
40
39
41
40
42 class BasicEngineServiceTest(DeferredTestCase,
41 class BasicEngineServiceTest(DeferredTestCase,
@@ -4,29 +4,28 b''
4
4
5 __docformat__ = "restructuredtext en"
5 __docformat__ = "restructuredtext en"
6
6
7 #-------------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
8 # Copyright (C) 2008 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-------------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-------------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 try:
18 # Tell nose to skip this module
19 from twisted.internet import defer
19 __test__ = {}
20 from IPython.testing.util import DeferredTestCase
20
21 from IPython.kernel.controllerservice import ControllerService
21 from twisted.internet import defer
22 from IPython.kernel import multiengine as me
22 from IPython.testing.util import DeferredTestCase
23 from IPython.kernel.tests.multienginetest import (IMultiEngineTestCase,
23 from IPython.kernel.controllerservice import ControllerService
24 ISynchronousMultiEngineTestCase)
24 from IPython.kernel import multiengine as me
25 except ImportError:
25 from IPython.kernel.tests.multienginetest import (IMultiEngineTestCase,
26 import nose
26 ISynchronousMultiEngineTestCase)
27 raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap")
27
28
28
29
30 class BasicMultiEngineTestCase(DeferredTestCase, IMultiEngineTestCase):
29 class BasicMultiEngineTestCase(DeferredTestCase, IMultiEngineTestCase):
31
30
32 def setUp(self):
31 def setUp(self):
@@ -14,25 +14,25 b' __docformat__ = "restructuredtext en"'
14 # Imports
14 # Imports
15 #-------------------------------------------------------------------------------
15 #-------------------------------------------------------------------------------
16
16
17 try:
17 # Tell nose to skip this module
18 from twisted.internet import defer, reactor
18 __test__ = {}
19
20 from twisted.internet import defer, reactor
21
22 from IPython.kernel.fcutil import Tub, UnauthenticatedTub
23
24 from IPython.testing.util import DeferredTestCase
25 from IPython.kernel.controllerservice import ControllerService
26 from IPython.kernel.multiengine import IMultiEngine
27 from IPython.kernel.tests.multienginetest import IFullSynchronousMultiEngineTestCase
28 from IPython.kernel.multienginefc import IFCSynchronousMultiEngine
29 from IPython.kernel import multiengine as me
30 from IPython.kernel.clientconnector import ClientConnector
31 from IPython.kernel.parallelfunction import ParallelFunction
32 from IPython.kernel.error import CompositeError
33 from IPython.kernel.util import printer
19
34
20 from IPython.kernel.fcutil import Tub, UnauthenticatedTub
21
35
22 from IPython.testing.util import DeferredTestCase
23 from IPython.kernel.controllerservice import ControllerService
24 from IPython.kernel.multiengine import IMultiEngine
25 from IPython.kernel.tests.multienginetest import IFullSynchronousMultiEngineTestCase
26 from IPython.kernel.multienginefc import IFCSynchronousMultiEngine
27 from IPython.kernel import multiengine as me
28 from IPython.kernel.clientconnector import ClientConnector
29 from IPython.kernel.parallelfunction import ParallelFunction
30 from IPython.kernel.error import CompositeError
31 from IPython.kernel.util import printer
32 except ImportError:
33 import nose
34 raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap")
35
36 def _raise_it(f):
36 def _raise_it(f):
37 try:
37 try:
38 f.raiseException()
38 f.raiseException()
@@ -4,36 +4,36 b''
4
4
5 __docformat__ = "restructuredtext en"
5 __docformat__ = "restructuredtext en"
6
6
7 #-------------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
8 # Copyright (C) 2008 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-------------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-------------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 try:
18 # Tell nose to skip this module
19 import zope.interface as zi
19 __test__ = {}
20 from twisted.trial import unittest
21 from IPython.testing.util import DeferredTestCase
22
20
23 from IPython.kernel.newserialized import \
21 import zope.interface as zi
24 ISerialized, \
22 from twisted.trial import unittest
25 IUnSerialized, \
23 from IPython.testing.util import DeferredTestCase
26 Serialized, \
27 UnSerialized, \
28 SerializeIt, \
29 UnSerializeIt
30 except ImportError:
31 import nose
32 raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap")
33
24
34 #-------------------------------------------------------------------------------
25 from IPython.kernel.newserialized import \
26 ISerialized, \
27 IUnSerialized, \
28 Serialized, \
29 UnSerialized, \
30 SerializeIt, \
31 UnSerializeIt
32
33
34 #-----------------------------------------------------------------------------
35 # Tests
35 # Tests
36 #-------------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37
37
38 class SerializedTestCase(unittest.TestCase):
38 class SerializedTestCase(unittest.TestCase):
39
39
@@ -16,18 +16,18 b' __docformat__ = "restructuredtext en"'
16 # Imports
16 # Imports
17 #-------------------------------------------------------------------------------
17 #-------------------------------------------------------------------------------
18
18
19 try:
19 # Tell nose to skip this module
20 from twisted.internet import defer
20 __test__ = {}
21 from twisted.python import failure
21
22
22 from twisted.internet import defer
23 from IPython.testing.util import DeferredTestCase
23 from twisted.python import failure
24 import IPython.kernel.pendingdeferred as pd
24
25 from IPython.kernel import error
25 from IPython.testing.util import DeferredTestCase
26 from IPython.kernel.util import printer
26 import IPython.kernel.pendingdeferred as pd
27 except ImportError:
27 from IPython.kernel import error
28 import nose
28 from IPython.kernel.util import printer
29 raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap")
29
30
30
31 class Foo(object):
31 class Foo(object):
32
32
33 def bar(self, bahz):
33 def bar(self, bahz):
@@ -15,19 +15,19 b' __docformat__ = "restructuredtext en"'
15 # Imports
15 # Imports
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17
17
18 try:
18 # Tell nose to skip this module
19 import time
19 __test__ = {}
20
20
21 from twisted.internet import defer
21 import time
22 from twisted.trial import unittest
22
23
23 from twisted.internet import defer
24 from IPython.kernel import task, controllerservice as cs, engineservice as es
24 from twisted.trial import unittest
25 from IPython.kernel.multiengine import IMultiEngine
25
26 from IPython.testing.util import DeferredTestCase
26 from IPython.kernel import task, controllerservice as cs, engineservice as es
27 from IPython.kernel.tests.tasktest import ITaskControllerTestCase
27 from IPython.kernel.multiengine import IMultiEngine
28 except ImportError:
28 from IPython.testing.util import DeferredTestCase
29 import nose
29 from IPython.kernel.tests.tasktest import ITaskControllerTestCase
30 raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap")
30
31
31
32 #-------------------------------------------------------------------------------
32 #-------------------------------------------------------------------------------
33 # Tests
33 # Tests
@@ -14,27 +14,26 b' __docformat__ = "restructuredtext en"'
14 # Imports
14 # Imports
15 #-------------------------------------------------------------------------------
15 #-------------------------------------------------------------------------------
16
16
17 try:
17 # Tell nose to skip this module
18 import time
18 __test__ = {}
19
19
20 from twisted.internet import defer, reactor
20 import time
21
21
22 from IPython.kernel.fcutil import Tub, UnauthenticatedTub
22 from twisted.internet import defer, reactor
23
23
24 from IPython.kernel import task as taskmodule
24 from IPython.kernel.fcutil import Tub, UnauthenticatedTub
25 from IPython.kernel import controllerservice as cs
25
26 import IPython.kernel.multiengine as me
26 from IPython.kernel import task as taskmodule
27 from IPython.testing.util import DeferredTestCase
27 from IPython.kernel import controllerservice as cs
28 from IPython.kernel.multienginefc import IFCSynchronousMultiEngine
28 import IPython.kernel.multiengine as me
29 from IPython.kernel.taskfc import IFCTaskController
29 from IPython.testing.util import DeferredTestCase
30 from IPython.kernel.util import printer
30 from IPython.kernel.multienginefc import IFCSynchronousMultiEngine
31 from IPython.kernel.tests.tasktest import ITaskControllerTestCase
31 from IPython.kernel.taskfc import IFCTaskController
32 from IPython.kernel.clientconnector import ClientConnector
32 from IPython.kernel.util import printer
33 from IPython.kernel.error import CompositeError
33 from IPython.kernel.tests.tasktest import ITaskControllerTestCase
34 from IPython.kernel.parallelfunction import ParallelFunction
34 from IPython.kernel.clientconnector import ClientConnector
35 except ImportError:
35 from IPython.kernel.error import CompositeError
36 import nose
36 from IPython.kernel.parallelfunction import ParallelFunction
37 raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap")
38
37
39
38
40 #-------------------------------------------------------------------------------
39 #-------------------------------------------------------------------------------
@@ -16,12 +16,15 b' __docformat__ = "restructuredtext en"'
16 # Imports
16 # Imports
17 #-------------------------------------------------------------------------------
17 #-------------------------------------------------------------------------------
18
18
19 import os, sys
19 import threading, Queue, atexit
20 import threading, Queue, atexit
20 import twisted
21
21
22 import twisted
22 from twisted.internet import defer, reactor
23 from twisted.internet import defer, reactor
23 from twisted.python import log, failure
24 from twisted.python import log, failure
24
25
26 from IPython.kernel.error import FileTimeoutError
27
25 #-------------------------------------------------------------------------------
28 #-------------------------------------------------------------------------------
26 # Classes related to twisted and threads
29 # Classes related to twisted and threads
27 #-------------------------------------------------------------------------------
30 #-------------------------------------------------------------------------------
@@ -204,3 +207,43 b' class DeferredList(defer.Deferred):'
204 result = None
207 result = None
205
208
206 return result
209 return result
210
211
212 def wait_for_file(filename, delay=0.1, max_tries=10):
213 """Wait (poll) for a file to be created.
214
215 This method returns a Deferred that will fire when a file exists. It
216 works by polling os.path.isfile in time intervals specified by the
217 delay argument. If `max_tries` is reached, it will errback with a
218 `FileTimeoutError`.
219
220 Parameters
221 ----------
222 filename : str
223 The name of the file to wait for.
224 delay : float
225 The time to wait between polls.
226 max_tries : int
227 The max number of attempts before raising `FileTimeoutError`
228
229 Returns
230 -------
231 d : Deferred
232 A Deferred instance that will fire when the file exists.
233 """
234
235 d = defer.Deferred()
236
237 def _test_for_file(filename, attempt=0):
238 if attempt >= max_tries:
239 d.errback(FileTimeoutError(
240 'timeout waiting for file to be created: %s' % filename
241 ))
242 else:
243 if os.path.isfile(filename):
244 d.callback(True)
245 else:
246 reactor.callLater(delay, _test_for_file, filename, attempt+1)
247
248 _test_for_file(filename)
249 return d
@@ -14,6 +14,7 b' for your operation system, from platutils_PLATFORMNAME module.'
14
14
15 import os
15 import os
16 import sys
16 import sys
17 import warnings
17
18
18 # Import the platform-specific implementations
19 # Import the platform-specific implementations
19 if os.name == 'posix':
20 if os.name == 'posix':
@@ -61,6 +62,41 b' def set_term_title(title):'
61 _platutils.set_term_title(title)
62 _platutils.set_term_title(title)
62
63
63
64
65 class FindCmdError(Exception):
66 pass
67
68 def find_cmd(cmd):
69 """Find full path to executable cmd in a cross platform manner.
70
71 This function tries to determine the full path to a command line program
72 using `which` on Unix/Linux/OS X and `win32api` on Windows. Most of the
73 time it will use the version that is first on the users `PATH`. If
74 cmd is `python` return `sys.executable`.
75
76 Parameters
77 ----------
78 cmd : str
79 The command line program to look for.
80 """
81 if cmd == 'python':
82 return sys.executable
83 try:
84 path = _platutils.find_cmd(cmd)
85 except:
86 raise FindCmdError('command could not be found: %s' % cmd)
87 # which returns empty if not found
88 if path == '':
89 raise FindCmdError('command could not be found: %s' % cmd)
90 return path
91
92 def get_long_path_name(path):
93 """Expand a path into its long form.
94
95 On Windows this expands any ~ in the paths. On other platforms, it is
96 a null operation.
97 """
98 return _platutils.get_long_path_name(path)
99
64 #-----------------------------------------------------------------------------
100 #-----------------------------------------------------------------------------
65 # Deprecated functions
101 # Deprecated functions
66 #-----------------------------------------------------------------------------
102 #-----------------------------------------------------------------------------
@@ -23,3 +23,11 b' ignore_termtitle = True'
23 def set_term_title(*args,**kw):
23 def set_term_title(*args,**kw):
24 """Dummy no-op."""
24 """Dummy no-op."""
25 pass
25 pass
26
27 def find_cmd(cmd):
28 """Find the full path to a command using which."""
29 return os.popen('which %s' % cmd).read().strip()
30
31 def get_long_path_name(path):
32 """Dummy no-op."""
33 return path
@@ -30,3 +30,11 b" if os.environ.get('TERM','') == 'xterm':"
30 set_term_title = _set_term_title_xterm
30 set_term_title = _set_term_title_xterm
31 else:
31 else:
32 set_term_title = _dummy_op
32 set_term_title = _dummy_op
33
34 def find_cmd(cmd):
35 """Find the full path to a command using which."""
36 return os.popen('which %s' % cmd).read().strip()
37
38 def get_long_path_name(path):
39 """Dummy no-op."""
40 return path
@@ -41,3 +41,42 b' except ImportError:'
41 if ret:
41 if ret:
42 # non-zero return code signals error, don't try again
42 # non-zero return code signals error, don't try again
43 ignore_termtitle = True
43 ignore_termtitle = True
44
45 def find_cmd(cmd):
46 """Find the full path to a .bat or .exe using the win32api module."""
47 try:
48 import win32api
49 except ImportError:
50 raise ImportError('you need to have pywin32 installed for this to work')
51 else:
52 try:
53 (path, offest) = win32api.SearchPath(os.environ['PATH'],cmd + '.exe')
54 except:
55 (path, offset) = win32api.SearchPath(os.environ['PATH'],cmd + '.bat')
56 return path
57
58
59 def get_long_path_name(path):
60 """Get a long path name (expand ~) on Windows using ctypes.
61
62 Examples
63 --------
64
65 >>> get_long_path_name('c:\\docume~1')
66 u'c:\\\\Documents and Settings'
67
68 """
69 try:
70 import ctypes
71 except ImportError:
72 raise ImportError('you need to have ctypes installed for this to work')
73 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
74 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
75 ctypes.c_uint ]
76
77 buf = ctypes.create_unicode_buffer(260)
78 rv = _GetLongPathName(path, buf, 260)
79 if rv == 0 or rv > 260:
80 return path
81 else:
82 return buf.value
@@ -123,10 +123,10 b' def skipif(skip_condition, msg=None):'
123 Parameters
123 Parameters
124 ----------
124 ----------
125 skip_condition : bool or callable.
125 skip_condition : bool or callable.
126 Flag to determine whether to skip test. If the condition is a
126 Flag to determine whether to skip test. If the condition is a
127 callable, it is used at runtime to dynamically make the decision. This
127 callable, it is used at runtime to dynamically make the decision. This
128 is useful for tests that may require costly imports, to delay the cost
128 is useful for tests that may require costly imports, to delay the cost
129 until the test suite is actually executed.
129 until the test suite is actually executed.
130 msg : string
130 msg : string
131 Message to give on raising a SkipTest exception
131 Message to give on raising a SkipTest exception
132
132
@@ -29,13 +29,6 b' def setastest(tf=True):'
29 tf : bool
29 tf : bool
30 If True specifies this is a test, not a test otherwise
30 If True specifies this is a test, not a test otherwise
31
31
32 e.g
33 >>> from numpy.testing.decorators import setastest
34 >>> @setastest(False)
35 ... def func_with_test_in_name(arg1, arg2): pass
36 ...
37 >>>
38
39 This decorator cannot use the nose namespace, because it can be
32 This decorator cannot use the nose namespace, because it can be
40 called from a non-test module. See also istest and nottest in
33 called from a non-test module. See also istest and nottest in
41 nose.tools
34 nose.tools
@@ -1,54 +1,121 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """IPython Test Suite Runner.
2 """IPython Test Suite Runner.
3
3
4 This module provides a main entry point to a user script to test IPython itself
4 This module provides a main entry point to a user script to test IPython
5 from the command line. The main() routine can be used in a similar manner to
5 itself from the command line. There are two ways of running this script:
6 the ``nosetests`` script, and it takes similar arguments, but if no arguments
6
7 are given it defaults to testing all of IPython. This should be preferred to
7 1. With the syntax `iptest all`. This runs our entire test suite by
8 using plain ``nosetests`` because a number of nose plugins necessary to test
8 calling this script (with different arguments) or trial recursively. This
9 IPython correctly are automatically configured by this code.
9 causes modules and package to be tested in different processes, using nose
10 or trial where appropriate.
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 the script simply calls nose, but with special command line flags and
13 plugins loaded.
14
15 For now, this script requires that both nose and twisted are installed. This
16 will change in the future.
10 """
17 """
11
18
12 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
13 # Module imports
20 # Module imports
14 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
15
22
16 # stdlib
23 import os
24 import os.path as path
17 import sys
25 import sys
26 import subprocess
27 import time
18 import warnings
28 import warnings
19
29
20 # third-party
21 import nose.plugins.builtin
30 import nose.plugins.builtin
22 from nose.core import TestProgram
31 from nose.core import TestProgram
23
32
24 # Our own imports
33 from IPython.platutils import find_cmd
25 from IPython.testing.plugin.ipdoctest import IPythonDoctest
34 from IPython.testing.plugin.ipdoctest import IPythonDoctest
26
35
36 pjoin = path.join
37
27 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
28 # Constants and globals
39 # Logic for skipping doctests
29 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
30
41
42 def test_for(mod):
43 """Test to see if mod is importable."""
44 try:
45 __import__(mod)
46 except ImportError:
47 return False
48 else:
49 return True
50
51 have_curses = test_for('_curses')
52 have_wx = test_for('wx')
53 have_zi = test_for('zope.interface')
54 have_twisted = test_for('twisted')
55 have_foolscap = test_for('foolscap')
56 have_objc = test_for('objc')
57 have_pexpect = test_for('pexpect')
58
31 # For the IPythonDoctest plugin, we need to exclude certain patterns that cause
59 # For the IPythonDoctest plugin, we need to exclude certain patterns that cause
32 # testing problems. We should strive to minimize the number of skipped
60 # testing problems. We should strive to minimize the number of skipped
33 # modules, since this means untested code. As the testing machinery
61 # modules, since this means untested code. As the testing machinery
34 # solidifies, this list should eventually become empty.
62 # solidifies, this list should eventually become empty.
35 EXCLUDE = ['IPython/external/',
63 EXCLUDE = [pjoin('IPython', 'external'),
36 'IPython/platutils_win32',
64 pjoin('IPython', 'frontend', 'process', 'winprocess.py'),
37 'IPython/frontend/cocoa',
65 pjoin('IPython_doctest_plugin'),
38 'IPython_doctest_plugin',
66 pjoin('IPython', 'Gnuplot'),
39 'IPython/Gnuplot',
67 pjoin('IPython', 'Extensions', 'ipy_'),
40 'IPython/Extensions/ipy_',
68 pjoin('IPython', 'Extensions', 'clearcmd'),
41 'IPython/Extensions/clearcmd',
69 pjoin('IPython', 'Extensions', 'PhysicalQInteractive'),
42 'IPython/Extensions/PhysicalQIn',
70 pjoin('IPython', 'Extensions', 'scitedirector'),
43 'IPython/Extensions/scitedirector',
71 pjoin('IPython', 'Extensions', 'numeric_formats'),
72 pjoin('IPython', 'testing', 'attic'),
73 pjoin('IPython', 'testing', 'tutils'),
74 pjoin('IPython', 'testing', 'tools'),
75 pjoin('IPython', 'testing', 'mkdoctests')
44 ]
76 ]
45
77
78 if not have_wx:
79 EXCLUDE.append(pjoin('IPython', 'Extensions', 'igrid'))
80 EXCLUDE.append(pjoin('IPython', 'gui'))
81 EXCLUDE.append(pjoin('IPython', 'frontend', 'wx'))
82
83 if not have_objc:
84 EXCLUDE.append(pjoin('IPython', 'frontend', 'cocoa'))
85
86 if not have_curses:
87 EXCLUDE.append(pjoin('IPython', 'Extensions', 'ibrowse'))
88
89 if not sys.platform == 'win32':
90 EXCLUDE.append(pjoin('IPython', 'platutils_win32'))
91
92 # These have to be skipped on win32 because the use echo, rm, cd, etc.
93 # See ticket https://bugs.launchpad.net/bugs/366982
94 if sys.platform == 'win32':
95 EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'test_exampleip'))
96 EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'dtexample'))
97
98 if not os.name == 'posix':
99 EXCLUDE.append(pjoin('IPython', 'platutils_posix'))
100
101 if not have_pexpect:
102 EXCLUDE.append(pjoin('IPython', 'irunner'))
103
104 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
105 if sys.platform == 'win32':
106 EXCLUDE = [s.replace('\\','\\\\') for s in EXCLUDE]
107
108
46 #-----------------------------------------------------------------------------
109 #-----------------------------------------------------------------------------
47 # Functions and classes
110 # Functions and classes
48 #-----------------------------------------------------------------------------
111 #-----------------------------------------------------------------------------
49
112
50 def main():
113 def run_iptest():
51 """Run the IPython test suite.
114 """Run the IPython test suite using nose.
115
116 This function is called when this script is **not** called with the form
117 `iptest all`. It simply calls nose with appropriate command line flags
118 and accepts all of the standard nose arguments.
52 """
119 """
53
120
54 warnings.filterwarnings('ignore',
121 warnings.filterwarnings('ignore',
@@ -60,8 +127,8 b' def main():'
60 # test suite back into working shape. Our nose
127 # test suite back into working shape. Our nose
61 # plugin needs to be gone through with a fine
128 # plugin needs to be gone through with a fine
62 # toothed comb to find what is causing the problem.
129 # toothed comb to find what is causing the problem.
63 # '--with-ipdoctest',
130 '--with-ipdoctest',
64 '--doctest-tests','--doctest-extension=txt',
131 '--ipdoctest-tests','--ipdoctest-extension=txt',
65 '--detailed-errors',
132 '--detailed-errors',
66
133
67 # We add --exe because of setuptools' imbecility (it
134 # We add --exe because of setuptools' imbecility (it
@@ -81,11 +148,13 b' def main():'
81 (':' in arg and '.py' in arg):
148 (':' in arg and '.py' in arg):
82 has_tests = True
149 has_tests = True
83 break
150 break
151
84 # If nothing was specifically requested, test full IPython
152 # If nothing was specifically requested, test full IPython
85 if not has_tests:
153 if not has_tests:
86 argv.append('IPython')
154 argv.append('IPython')
87
155
88 # Construct list of plugins, omitting the existing doctest plugin.
156 # Construct list of plugins, omitting the existing doctest plugin, which
157 # ours replaces (and extends).
89 plugins = [IPythonDoctest(EXCLUDE)]
158 plugins = [IPythonDoctest(EXCLUDE)]
90 for p in nose.plugins.builtin.plugins:
159 for p in nose.plugins.builtin.plugins:
91 plug = p()
160 plug = p()
@@ -96,3 +165,136 b' def main():'
96 plugins.append(plug)
165 plugins.append(plug)
97
166
98 TestProgram(argv=argv,plugins=plugins)
167 TestProgram(argv=argv,plugins=plugins)
168
169
170 class IPTester(object):
171 """Call that calls iptest or trial in a subprocess.
172 """
173 def __init__(self,runner='iptest',params=None):
174 """ """
175 if runner == 'iptest':
176 self.runner = ['iptest','-v']
177 else:
178 self.runner = [find_cmd('trial')]
179 if params is None:
180 params = []
181 if isinstance(params,str):
182 params = [params]
183 self.params = params
184
185 # Assemble call
186 self.call_args = self.runner+self.params
187
188 def run(self):
189 """Run the stored commands"""
190 return subprocess.call(self.call_args)
191
192
193 def make_runners():
194 """Define the modules and packages that need to be tested.
195 """
196
197 # This omits additional top-level modules that should not be doctested.
198 # XXX: Shell.py is also ommited because of a bug in the skip_doctest
199 # decorator. See ticket https://bugs.launchpad.net/bugs/366209
200 top_mod = \
201 ['background_jobs.py', 'ColorANSI.py', 'completer.py', 'ConfigLoader.py',
202 'CrashHandler.py', 'Debugger.py', 'deep_reload.py', 'demo.py',
203 'DPyGetOpt.py', 'dtutils.py', 'excolors.py', 'FakeModule.py',
204 'generics.py', 'genutils.py', 'history.py', 'hooks.py', 'ipapi.py',
205 'iplib.py', 'ipmaker.py', 'ipstruct.py', 'Itpl.py',
206 'Logger.py', 'macro.py', 'Magic.py', 'OInspect.py',
207 'OutputTrap.py', 'platutils.py', 'prefilter.py', 'Prompts.py',
208 'PyColorize.py', 'Release.py', 'rlineimpl.py', 'shadowns.py',
209 'shellglobals.py', 'strdispatch.py', 'twshell.py',
210 'ultraTB.py', 'upgrade_dir.py', 'usage.py', 'wildcard.py',
211 # See note above for why this is skipped
212 # 'Shell.py',
213 'winconsole.py']
214
215 if have_pexpect:
216 top_mod.append('irunner.py')
217
218 if sys.platform == 'win32':
219 top_mod.append('platutils_win32.py')
220 elif os.name == 'posix':
221 top_mod.append('platutils_posix.py')
222 else:
223 top_mod.append('platutils_dummy.py')
224
225 # These are tested by nose, so skip IPython.kernel
226 top_pack = ['config','Extensions','frontend',
227 'testing','tests','tools','UserConfig']
228
229 if have_wx:
230 top_pack.append('gui')
231
232 modules = ['IPython.%s' % m[:-3] for m in top_mod ]
233 packages = ['IPython.%s' % m for m in top_pack ]
234
235 # Make runners
236 runners = dict(zip(top_pack, [IPTester(params=v) for v in packages]))
237
238 # Test IPython.kernel using trial if twisted is installed
239 if have_zi and have_twisted and have_foolscap:
240 runners['trial'] = IPTester('trial',['IPython'])
241
242 runners['modules'] = IPTester(params=modules)
243
244 return runners
245
246
247 def run_iptestall():
248 """Run the entire IPython test suite by calling nose and trial.
249
250 This function constructs :class:`IPTester` instances for all IPython
251 modules and package and then runs each of them. This causes the modules
252 and packages of IPython to be tested each in their own subprocess using
253 nose or twisted.trial appropriately.
254 """
255 runners = make_runners()
256 # Run all test runners, tracking execution time
257 failed = {}
258 t_start = time.time()
259 for name,runner in runners.iteritems():
260 print '*'*77
261 print 'IPython test set:',name
262 res = runner.run()
263 if res:
264 failed[name] = res
265 t_end = time.time()
266 t_tests = t_end - t_start
267 nrunners = len(runners)
268 nfail = len(failed)
269 # summarize results
270 print
271 print '*'*77
272 print 'Ran %s test sets in %.3fs' % (nrunners, t_tests)
273 print
274 if not failed:
275 print 'OK'
276 else:
277 # If anything went wrong, point out what command to rerun manually to
278 # see the actual errors and individual summary
279 print 'ERROR - %s out of %s test sets failed.' % (nfail, nrunners)
280 for name in failed:
281 failed_runner = runners[name]
282 print '-'*40
283 print 'Runner failed:',name
284 print 'You may wish to rerun this one individually, with:'
285 print ' '.join(failed_runner.call_args)
286 print
287
288
289 def main():
290 if len(sys.argv) == 1:
291 run_iptestall()
292 else:
293 if sys.argv[1] == 'all':
294 run_iptestall()
295 else:
296 run_iptest()
297
298
299 if __name__ == '__main__':
300 main() No newline at end of file
@@ -15,7 +15,6 b' Limitations:'
15 won't even have these special _NN variables set at all.
15 won't even have these special _NN variables set at all.
16 """
16 """
17
17
18
19 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
20 # Module imports
19 # Module imports
21
20
@@ -60,6 +59,19 b' log = logging.getLogger(__name__)'
60 # machinery into a fit. This code should be considered a gross hack, but it
59 # machinery into a fit. This code should be considered a gross hack, but it
61 # gets the job done.
60 # gets the job done.
62
61
62 def default_argv():
63 """Return a valid default argv for creating testing instances of ipython"""
64
65 # Get the install directory for the user configuration and tell ipython to
66 # use the default profile from there.
67 from IPython import UserConfig
68 ipcdir = os.path.dirname(UserConfig.__file__)
69 #ipconf = os.path.join(ipcdir,'ipy_user_conf.py')
70 ipconf = os.path.join(ipcdir,'ipythonrc')
71 #print 'conf:',ipconf # dbg
72
73 return ['--colors=NoColor','--noterm_title','-rcfile=%s' % ipconf]
74
63
75
64 # Hack to modify the %run command so we can sync the user's namespace with the
76 # Hack to modify the %run command so we can sync the user's namespace with the
65 # test globals. Once we move over to a clean magic system, this will be done
77 # test globals. Once we move over to a clean magic system, this will be done
@@ -85,9 +97,19 b' def _run_ns_sync(self,arg_s,runner=None):'
85 This is strictly needed for running doctests that call %run.
97 This is strictly needed for running doctests that call %run.
86 """
98 """
87
99
88 finder = py_file_finder(_run_ns_sync.test_filename)
100 # When tests call %run directly (not via doctest) these function attributes
101 # are not set
102 try:
103 fname = _run_ns_sync.test_filename
104 except AttributeError:
105 fname = arg_s
106
107 finder = py_file_finder(fname)
89 out = _ip.IP.magic_run_ori(arg_s,runner,finder)
108 out = _ip.IP.magic_run_ori(arg_s,runner,finder)
90 _run_ns_sync.test_globs.update(_ip.user_ns)
109
110 # Simliarly, there is no test_globs when a test is NOT a doctest
111 if hasattr(_run_ns_sync,'test_globs'):
112 _run_ns_sync.test_globs.update(_ip.user_ns)
91 return out
113 return out
92
114
93
115
@@ -114,15 +136,31 b' class ipnsdict(dict):'
114 def update(self,other):
136 def update(self,other):
115 self._checkpoint()
137 self._checkpoint()
116 dict.update(self,other)
138 dict.update(self,other)
139
117 # If '_' is in the namespace, python won't set it when executing code,
140 # If '_' is in the namespace, python won't set it when executing code,
118 # and we have examples that test it. So we ensure that the namespace
141 # and we have examples that test it. So we ensure that the namespace
119 # is always 'clean' of it before it's used for test code execution.
142 # is always 'clean' of it before it's used for test code execution.
120 self.pop('_',None)
143 self.pop('_',None)
144
145 # The builtins namespace must *always* be the real __builtin__ module,
146 # else weird stuff happens. The main ipython code does have provisions
147 # to ensure this after %run, but since in this class we do some
148 # aggressive low-level cleaning of the execution namespace, we need to
149 # correct for that ourselves, to ensure consitency with the 'real'
150 # ipython.
151 self['__builtins__'] = __builtin__
121
152
122
153
123 def start_ipython():
154 def start_ipython():
124 """Start a global IPython shell, which we need for IPython-specific syntax.
155 """Start a global IPython shell, which we need for IPython-specific syntax.
125 """
156 """
157
158 # This function should only ever run once!
159 if hasattr(start_ipython,'already_called'):
160 return
161 start_ipython.already_called = True
162
163 # Ok, first time we're called, go ahead
126 import new
164 import new
127
165
128 import IPython
166 import IPython
@@ -142,10 +180,11 b' def start_ipython():'
142 _excepthook = sys.excepthook
180 _excepthook = sys.excepthook
143 _main = sys.modules.get('__main__')
181 _main = sys.modules.get('__main__')
144
182
183 argv = default_argv()
184
145 # Start IPython instance. We customize it to start with minimal frills.
185 # Start IPython instance. We customize it to start with minimal frills.
146 user_ns,global_ns = IPython.ipapi.make_user_namespaces(ipnsdict(),dict())
186 user_ns,global_ns = IPython.ipapi.make_user_namespaces(ipnsdict(),dict())
147 IPython.Shell.IPShell(['--colors=NoColor','--noterm_title'],
187 IPython.Shell.IPShell(argv,user_ns,global_ns)
148 user_ns,global_ns)
149
188
150 # Deactivate the various python system hooks added by ipython for
189 # Deactivate the various python system hooks added by ipython for
151 # interactive convenience so we don't confuse the doctest system
190 # interactive convenience so we don't confuse the doctest system
@@ -691,6 +730,7 b' class ExtensionDoctest(doctests.Doctest):'
691 to exclude any filename which matches them from inclusion in the test
730 to exclude any filename which matches them from inclusion in the test
692 suite (using pattern.search(), NOT pattern.match() ).
731 suite (using pattern.search(), NOT pattern.match() ).
693 """
732 """
733
694 if exclude_patterns is None:
734 if exclude_patterns is None:
695 exclude_patterns = []
735 exclude_patterns = []
696 self.exclude_patterns = map(re.compile,exclude_patterns)
736 self.exclude_patterns = map(re.compile,exclude_patterns)
@@ -800,11 +840,11 b' class ExtensionDoctest(doctests.Doctest):'
800 Modified version that accepts extension modules as valid containers for
840 Modified version that accepts extension modules as valid containers for
801 doctests.
841 doctests.
802 """
842 """
803 #print '*** ipdoctest- wantFile:',filename # dbg
843 # print '*** ipdoctest- wantFile:',filename # dbg
804
844
805 for pat in self.exclude_patterns:
845 for pat in self.exclude_patterns:
806 if pat.search(filename):
846 if pat.search(filename):
807 #print '###>>> SKIP:',filename # dbg
847 # print '###>>> SKIP:',filename # dbg
808 return False
848 return False
809
849
810 if is_extension_module(filename):
850 if is_extension_module(filename):
@@ -836,15 +876,33 b' class IPythonDoctest(ExtensionDoctest):'
836 optionflags=optionflags,
876 optionflags=optionflags,
837 checker=self.checker)
877 checker=self.checker)
838
878
839 def configure(self, options, config):
879 def options(self, parser, env=os.environ):
880 Plugin.options(self, parser, env)
881 parser.add_option('--ipdoctest-tests', action='store_true',
882 dest='ipdoctest_tests',
883 default=env.get('NOSE_IPDOCTEST_TESTS',True),
884 help="Also look for doctests in test modules. "
885 "Note that classes, methods and functions should "
886 "have either doctests or non-doctest tests, "
887 "not both. [NOSE_IPDOCTEST_TESTS]")
888 parser.add_option('--ipdoctest-extension', action="append",
889 dest="ipdoctest_extension",
890 help="Also look for doctests in files with "
891 "this extension [NOSE_IPDOCTEST_EXTENSION]")
892 # Set the default as a list, if given in env; otherwise
893 # an additional value set on the command line will cause
894 # an error.
895 env_setting = env.get('NOSE_IPDOCTEST_EXTENSION')
896 if env_setting is not None:
897 parser.set_defaults(ipdoctest_extension=tolist(env_setting))
840
898
899 def configure(self, options, config):
841 Plugin.configure(self, options, config)
900 Plugin.configure(self, options, config)
842 self.doctest_tests = options.doctest_tests
901 self.doctest_tests = options.ipdoctest_tests
843 self.extension = tolist(options.doctestExtension)
902 self.extension = tolist(options.ipdoctest_extension)
844
903
845 self.parser = IPDocTestParser()
904 self.parser = IPDocTestParser()
846 self.finder = DocTestFinder(parser=self.parser)
905 self.finder = DocTestFinder(parser=self.parser)
847 self.checker = IPDoctestOutputChecker()
906 self.checker = IPDoctestOutputChecker()
848 self.globs = None
907 self.globs = None
849 self.extraglobs = None
908 self.extraglobs = None
850
@@ -6,8 +6,9 b' This is used by a companion test case.'
6 import gc
6 import gc
7
7
8 class C(object):
8 class C(object):
9 def __del__(self):
9 def __del__(self):
10 print 'deleting object...'
10 pass
11 #print 'deleting object...' # dbg
11
12
12 c = C()
13 c = C()
13
14
@@ -39,13 +39,10 b' def doctest_ivars():'
39 Out[6]: 1
39 Out[6]: 1
40 """
40 """
41
41
42 @dec.skip_doctest
42 #@dec.skip_doctest
43 def doctest_refs():
43 def doctest_refs():
44 """DocTest reference holding issues when running scripts.
44 """DocTest reference holding issues when running scripts.
45
45
46 In [32]: run show_refs.py
46 In [32]: run show_refs.py
47 c referrers: [<type 'dict'>]
47 c referrers: [<type 'dict'>]
48
49 In [33]: map(type,gc.get_referrers(c))
50 Out[33]: [<type 'dict'>]
51 """
48 """
@@ -46,7 +46,7 b' def test_deliberately_broken():'
46 """A deliberately broken test - we want to skip this one."""
46 """A deliberately broken test - we want to skip this one."""
47 1/0
47 1/0
48
48
49 @dec.skip('foo')
49 @dec.skip('Testing the skip decorator')
50 def test_deliberately_broken2():
50 def test_deliberately_broken2():
51 """Another deliberately broken test - we want to skip this one."""
51 """Another deliberately broken test - we want to skip this one."""
52 1/0
52 1/0
@@ -26,7 +26,7 b' import sys'
26
26
27 class A(object):
27 class A(object):
28 def __del__(self):
28 def __del__(self):
29 print 'object A deleted'
29 print 'obj_del.py: object A deleted'
30
30
31 a = A()
31 a = A()
32
32
@@ -16,11 +16,12 b' class C(object):'
16 self.name = name
16 self.name = name
17
17
18 def __del__(self):
18 def __del__(self):
19 print 'Deleting object:',self.name
19 print 'tclass.py: deleting object:',self.name
20
20
21 try:
21 try:
22 name = sys.argv[1]
22 name = sys.argv[1]
23 except IndexError:
23 except IndexError:
24 pass
24 pass
25 else:
25 else:
26 c = C(name)
26 if name.startswith('C'):
27 c = C(name)
@@ -210,7 +210,6 b' def test_get_home_dir_7():'
210 home_dir = genutils.get_home_dir()
210 home_dir = genutils.get_home_dir()
211 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
211 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
212
212
213
214 #
213 #
215 # Tests for get_ipython_dir
214 # Tests for get_ipython_dir
216 #
215 #
@@ -239,7 +238,6 b' def test_get_ipython_dir_3():'
239 ipdir = genutils.get_ipython_dir()
238 ipdir = genutils.get_ipython_dir()
240 nt.assert_equal(ipdir, os.path.abspath(os.path.join("someplace", "_ipython")))
239 nt.assert_equal(ipdir, os.path.abspath(os.path.join("someplace", "_ipython")))
241
240
242
243 #
241 #
244 # Tests for get_security_dir
242 # Tests for get_security_dir
245 #
243 #
@@ -249,6 +247,14 b' def test_get_security_dir():'
249 """Testcase to see if we can call get_security_dir without Exceptions."""
247 """Testcase to see if we can call get_security_dir without Exceptions."""
250 sdir = genutils.get_security_dir()
248 sdir = genutils.get_security_dir()
251
249
250 #
251 # Tests for get_log_dir
252 #
253
254 @with_enivronment
255 def test_get_log_dir():
256 """Testcase to see if we can call get_log_dir without Exceptions."""
257 sdir = genutils.get_log_dir()
252
258
253 #
259 #
254 # Tests for popkey
260 # Tests for popkey
@@ -289,3 +295,12 b' def test_popkey_3():'
289 nt.assert_equal(dct, dict(c=3))
295 nt.assert_equal(dct, dict(c=3))
290 nt.assert_equal(genutils.popkey(dct, "c"), 3)
296 nt.assert_equal(genutils.popkey(dct, "c"), 3)
291 nt.assert_equal(dct, dict())
297 nt.assert_equal(dct, dict())
298
299
300 def test_filefind():
301 """Various tests for filefind"""
302 f = tempfile.NamedTemporaryFile()
303 print 'fname:',f.name
304 alt_dirs = genutils.get_ipython_dir()
305 t = genutils.filefind(f.name,alt_dirs)
306 print 'found:',t
@@ -1,17 +1,80 b''
1 """Tests for the key iplib module, where the main ipython class is defined.
1 """Tests for the key iplib module, where the main ipython class is defined.
2 """
2 """
3 #-----------------------------------------------------------------------------
4 # Module imports
5 #-----------------------------------------------------------------------------
3
6
7 # stdlib
8 import os
9 import shutil
10 import tempfile
11
12 # third party
4 import nose.tools as nt
13 import nose.tools as nt
5
14
15 # our own packages
16 from IPython import ipapi, iplib
17
18 #-----------------------------------------------------------------------------
19 # Globals
20 #-----------------------------------------------------------------------------
21
22 # Useful global ipapi object and main IPython one. Unfortunately we have a
23 # long precedent of carrying the 'ipapi' global object which is injected into
24 # the system namespace as _ip, but that keeps a pointer to the actual IPython
25 # InteractiveShell instance, which is named IP. Since in testing we do need
26 # access to the real thing (we want to probe beyond what ipapi exposes), make
27 # here a global reference to each. In general, things that are exposed by the
28 # ipapi instance should be read from there, but we also will often need to use
29 # the actual IPython one.
30
31 # Get the public instance of IPython, and if it's None, make one so we can use
32 # it for testing
33 ip = ipapi.get()
34 if ip is None:
35 # IPython not running yet, make one from the testing machinery for
36 # consistency when the test suite is being run via iptest
37 from IPython.testing.plugin import ipdoctest
38 ip = ipapi.get()
39
40 IP = ip.IP # This is the actual IPython shell 'raw' object.
41
42 #-----------------------------------------------------------------------------
43 # Test functions
44 #-----------------------------------------------------------------------------
6
45
7 def test_reset():
46 def test_reset():
8 """reset must clear most namespaces."""
47 """reset must clear most namespaces."""
9 ip = _ip.IP
48 IP.reset() # first, it should run without error
10 ip.reset() # first, it should run without error
11 # Then, check that most namespaces end up empty
49 # Then, check that most namespaces end up empty
12 for ns in ip.ns_refs_table:
50 for ns in IP.ns_refs_table:
13 if ns is ip.user_ns:
51 if ns is IP.user_ns:
14 # The user namespace is reset with some data, so we can't check for
52 # The user namespace is reset with some data, so we can't check for
15 # it being empty
53 # it being empty
16 continue
54 continue
17 nt.assert_equals(len(ns),0)
55 nt.assert_equals(len(ns),0)
56
57
58 # make sure that user_setup can be run re-entrantly in 'install' mode.
59 def test_user_setup():
60 # use a lambda to pass kwargs to the generator
61 user_setup = lambda a,k: iplib.user_setup(*a,**k)
62 kw = dict(mode='install', interactive=False)
63
64 # Call the user setup and verify that the directory exists
65 yield user_setup, (ip.options.ipythondir,''), kw
66 yield os.path.isdir, ip.options.ipythondir
67
68 # Now repeat the operation with a non-existent directory. Check both that
69 # the call succeeds and that the directory is created.
70 tmpdir = tempfile.mktemp(prefix='ipython-test-')
71 # Use a try with an empty except because try/finally doesn't work with a
72 # yield in Python 2.4.
73 try:
74 yield user_setup, (tmpdir,''), kw
75 yield os.path.isdir, tmpdir
76 except:
77 pass
78 # Clean up the temp dir once done
79 shutil.rmtree(tmpdir)
80 No newline at end of file
@@ -3,15 +3,16 b''
3 Needs to be run by nose (to make ipython session available).
3 Needs to be run by nose (to make ipython session available).
4 """
4 """
5
5
6 # Standard library imports
7 import os
6 import os
8 import sys
7 import sys
8 import tempfile
9 import types
9
10
10 # Third-party imports
11 import nose.tools as nt
11 import nose.tools as nt
12
12
13 # From our own code
13 from IPython.platutils import find_cmd, get_long_path_name
14 from IPython.testing import decorators as dec
14 from IPython.testing import decorators as dec
15 from IPython.testing import tools as tt
15
16
16 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
17 # Test functions begin
18 # Test functions begin
@@ -34,28 +35,6 b' def test_rehashx():'
34 assert len(scoms) > 10
35 assert len(scoms) > 10
35
36
36
37
37 def doctest_run_ns():
38 """Classes declared %run scripts must be instantiable afterwards.
39
40 In [11]: run tclass
41
42 In [12]: isinstance(f(),foo)
43 Out[12]: True
44 """
45
46
47 def doctest_run_ns2():
48 """Classes declared %run scripts must be instantiable afterwards.
49
50 In [3]: run tclass.py
51
52 In [4]: run tclass first_pass
53
54 In [5]: run tclass second_pass
55 Deleting object: first_pass
56 """
57
58
59 def doctest_hist_f():
38 def doctest_hist_f():
60 """Test %hist -f with temporary filename.
39 """Test %hist -f with temporary filename.
61
40
@@ -79,13 +58,15 b' def doctest_hist_r():'
79 hist -n -r 2 # random
58 hist -n -r 2 # random
80 """
59 """
81
60
82
61 # This test is known to fail on win32.
62 # See ticket https://bugs.launchpad.net/bugs/366334
83 def test_obj_del():
63 def test_obj_del():
84 """Test that object's __del__ methods are called on exit."""
64 """Test that object's __del__ methods are called on exit."""
85 test_dir = os.path.dirname(__file__)
65 test_dir = os.path.dirname(__file__)
86 del_file = os.path.join(test_dir,'obj_del.py')
66 del_file = os.path.join(test_dir,'obj_del.py')
87 out = _ip.IP.getoutput('ipython %s' % del_file)
67 ipython_cmd = find_cmd('ipython')
88 nt.assert_equals(out,'object A deleted')
68 out = _ip.IP.getoutput('%s %s' % (ipython_cmd, del_file))
69 nt.assert_equals(out,'obj_del.py: object A deleted')
89
70
90
71
91 def test_shist():
72 def test_shist():
@@ -133,3 +114,136 b' def test_fail_dec2(*a,**k):'
133 def test_fail_dec3(*a,**k):
114 def test_fail_dec3(*a,**k):
134 yield nt.assert_true, False
115 yield nt.assert_true, False
135
116
117
118 def doctest_refbug():
119 """Very nasty problem with references held by multiple runs of a script.
120 See: https://bugs.launchpad.net/ipython/+bug/269966
121
122 In [1]: _ip.IP.clear_main_mod_cache()
123
124 In [2]: run refbug
125
126 In [3]: call_f()
127 lowercased: hello
128
129 In [4]: run refbug
130
131 In [5]: call_f()
132 lowercased: hello
133 lowercased: hello
134 """
135
136 #-----------------------------------------------------------------------------
137 # Tests for %run
138 #-----------------------------------------------------------------------------
139
140 # %run is critical enough that it's a good idea to have a solid collection of
141 # tests for it, some as doctests and some as normal tests.
142
143 def doctest_run_ns():
144 """Classes declared %run scripts must be instantiable afterwards.
145
146 In [11]: run tclass foo
147
148 In [12]: isinstance(f(),foo)
149 Out[12]: True
150 """
151
152
153 def doctest_run_ns2():
154 """Classes declared %run scripts must be instantiable afterwards.
155
156 In [4]: run tclass C-first_pass
157
158 In [5]: run tclass C-second_pass
159 tclass.py: deleting object: C-first_pass
160 """
161
162 @dec.skip_win32
163 def doctest_run_builtins():
164 """Check that %run doesn't damage __builtins__ via a doctest.
165
166 This is similar to the test_run_builtins, but I want *both* forms of the
167 test to catch any possible glitches in our testing machinery, since that
168 modifies %run somewhat. So for this, we have both a normal test (below)
169 and a doctest (this one).
170
171 In [1]: import tempfile
172
173 In [2]: bid1 = id(__builtins__)
174
175 In [3]: f = tempfile.NamedTemporaryFile()
176
177 In [4]: f.write('pass\\n')
178
179 In [5]: f.flush()
180
181 In [6]: print 'B1:',type(__builtins__)
182 B1: <type 'module'>
183
184 In [7]: %run $f.name
185
186 In [8]: bid2 = id(__builtins__)
187
188 In [9]: print 'B2:',type(__builtins__)
189 B2: <type 'module'>
190
191 In [10]: bid1 == bid2
192 Out[10]: True
193 """
194
195 # For some tests, it will be handy to organize them in a class with a common
196 # setup that makes a temp file
197
198 class TestMagicRun(object):
199
200 def setup(self):
201 """Make a valid python temp file."""
202 f = tempfile.NamedTemporaryFile()
203 f.write('pass\n')
204 f.flush()
205 self.tmpfile = f
206
207 def run_tmpfile(self):
208 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
209 # See below and ticket https://bugs.launchpad.net/bugs/366353
210 _ip.magic('run %s' % self.tmpfile.name)
211
212 # See https://bugs.launchpad.net/bugs/366353
213 @dec.skip_if_not_win32
214 def test_run_tempfile_path(self):
215 tt.assert_equals(True,False,"%run doesn't work with tempfile paths on win32.")
216
217 # See https://bugs.launchpad.net/bugs/366353
218 @dec.skip_win32
219 def test_builtins_id(self):
220 """Check that %run doesn't damage __builtins__ """
221
222 # Test that the id of __builtins__ is not modified by %run
223 bid1 = id(_ip.user_ns['__builtins__'])
224 self.run_tmpfile()
225 bid2 = id(_ip.user_ns['__builtins__'])
226 tt.assert_equals(bid1, bid2)
227
228 # See https://bugs.launchpad.net/bugs/366353
229 @dec.skip_win32
230 def test_builtins_type(self):
231 """Check that the type of __builtins__ doesn't change with %run.
232
233 However, the above could pass if __builtins__ was already modified to
234 be a dict (it should be a module) by a previous use of %run. So we
235 also check explicitly that it really is a module:
236 """
237 self.run_tmpfile()
238 tt.assert_equals(type(_ip.user_ns['__builtins__']),type(sys))
239
240 # See https://bugs.launchpad.net/bugs/366353
241 @dec.skip_win32
242 def test_prompts(self):
243 """Test that prompts correctly generate after %run"""
244 self.run_tmpfile()
245 p2 = str(_ip.IP.outputcache.prompt2).strip()
246 nt.assert_equals(p2[:3], '...')
247
248 def teardown(self):
249 self.tmpfile.close()
@@ -25,6 +25,7 b" if __name__ == '__main__':"
25 r'\.cocoa',
25 r'\.cocoa',
26 r'\.ipdoctest',
26 r'\.ipdoctest',
27 r'\.Gnuplot',
27 r'\.Gnuplot',
28 r'\.frontend.process.winprocess',
28 ]
29 ]
29 docwriter.write_api_docs(outdir)
30 docwriter.write_api_docs(outdir)
30 docwriter.write_index(outdir, 'gen',
31 docwriter.write_index(outdir, 'gen',
@@ -75,6 +75,9 b' Bug fixes'
75 The block is assigned to pasted_block even if code
75 The block is assigned to pasted_block even if code
76 raises exception.
76 raises exception.
77
77
78 * Bug #274067 'The code in get_home_dir is broken for py2exe' was
79 fixed.
80
78 Backwards incompatible changes
81 Backwards incompatible changes
79 ------------------------------
82 ------------------------------
80
83
@@ -345,6 +345,37 b' nosetests option. For example, you can use ``--pdb`` or ``--pdb-failures`` to'
345 automatically activate the interactive Pdb debugger on errors or failures. See
345 automatically activate the interactive Pdb debugger on errors or failures. See
346 the nosetests documentation for further details.
346 the nosetests documentation for further details.
347
347
348 .. warning::
349
350 Note that right now we have a nasty interaction between ipdoctest and
351 twisted. Until we figure this out, please use the following instructions to
352 ensure that at least you run all the tests.
353
354 Right now, if you now run::
355
356 $ iptest [any options] [any submodules]
357
358 it will NOT load ipdoctest but won't cause any Twisted problems.
359
360 Once you're happy that you didn't break Twisted, run::
361
362 $ iptest --with-ipdoctest [any options] [any submodules]
363
364 This MAY give a Twisted AlreadyCalledError exception at the end, but it will
365 also correctly load up all of the ipython-specific tests and doctests.
366
367 The above can be made easier with a trivial shell alias::
368
369 $ alias iptest2='iptest --with-ipdoctest'
370
371 So that you can run::
372
373 $ iptest ...
374 # Twisted happy
375 # iptest2 ...
376 # ignore possible Twisted error, this checks all the rest.
377
378
348 A few tips for writing tests
379 A few tips for writing tests
349 ----------------------------
380 ----------------------------
350
381
@@ -302,18 +302,33 b' The ``--furl-file`` flag works like this::'
302 Make FURL files persistent
302 Make FURL files persistent
303 ---------------------------
303 ---------------------------
304
304
305 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.
305 At fist glance it may seem that that managing the FURL files is a bit
306 annoying. Going back to the house and key analogy, copying the FURL around
307 each time you start the controller is like having to make a new key every time
308 you want to unlock the door and enter your house. As with your house, you want
309 to be able to create the key (or FURL file) once, and then simply use it at
310 any point in the future.
306
311
307 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::
312 This is possible, but before you do this, you **must** remove any old FURL
313 files in the :file:`~/.ipython/security` directory.
314
315 .. warning::
316
317 You **must** remove old FURL files before using persistent FURL files.
318
319 Then, The only thing you have to do is decide what ports the controller will
320 listen on for the engines and clients. This is done as follows::
308
321
309 $ ipcontroller -r --client-port=10101 --engine-port=10102
322 $ ipcontroller -r --client-port=10101 --engine-port=10102
310
323
311 These options also work with all of the various modes of
324 These options also work with all of the various modes of
312 :command:`ipcluster`::
325 :command:`ipcluster`::
313
326
314 $ ipcluster local -n 2 -r --client-port=10101 --engine-port=10102
327 $ ipcluster local -n 2 -r --client-port=10101 --engine-port=10102
315
328
316 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.
329 Then, just copy the furl files over the first time and you are set. You can
330 start and stop the controller and engines any many times as you want in the
331 future, just make sure to tell the controller to use the *same* ports.
317
332
318 .. note::
333 .. note::
319
334
@@ -2,6 +2,10 b''
2 IPython/Vision Beam Pattern Demo
2 IPython/Vision Beam Pattern Demo
3 ==================================
3 ==================================
4
4
5 .. note::
6
7 This page has not been updated to reflect the recent work on ipcluster.
8 This work makes it much easier to use IPython on a cluster.
5
9
6 Installing and testing IPython at OSC systems
10 Installing and testing IPython at OSC systems
7 =============================================
11 =============================================
@@ -5,9 +5,15 b''
5
5
6 from docutils.nodes import Body, Element
6 from docutils.nodes import Body, Element
7 from docutils.writers.html4css1 import HTMLTranslator
7 from docutils.writers.html4css1 import HTMLTranslator
8 from sphinx.latexwriter import LaTeXTranslator
9 from docutils.parsers.rst import directives
8 from docutils.parsers.rst import directives
10
9
10 # The sphinx API has changed, so we try both the old and new import forms
11 try:
12 from sphinx.latexwriter import LaTeXTranslator
13 except ImportError:
14 from sphinx.writers.latex import LaTeXTranslator
15
16
11 class html_only(Body, Element):
17 class html_only(Body, Element):
12 pass
18 pass
13
19
@@ -109,7 +109,7 b' def find_packages():'
109 add_package(packages, 'gui')
109 add_package(packages, 'gui')
110 add_package(packages, 'gui.wx')
110 add_package(packages, 'gui.wx')
111 add_package(packages, 'frontend', tests=True)
111 add_package(packages, 'frontend', tests=True)
112 add_package(packages, 'frontend._process')
112 add_package(packages, 'frontend.process')
113 add_package(packages, 'frontend.wx')
113 add_package(packages, 'frontend.wx')
114 add_package(packages, 'frontend.cocoa', tests=True)
114 add_package(packages, 'frontend.cocoa', tests=True)
115 add_package(packages, 'kernel', config=True, tests=True, scripts=True)
115 add_package(packages, 'kernel', config=True, tests=True, scripts=True)
General Comments 0
You need to be logged in to leave comments. Login now