Show More
@@ -0,0 +1,37 | |||||
|
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 | |||||
|
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 | |||||
|
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 | |||||
|
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 | |||||
|
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 | |||||
|
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 | |||||
|
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 | |||||
|
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 | |||||
|
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 | |||||
|
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 | |||||
|
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 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 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 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 i |
|
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 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 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 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 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 = |
|
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 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]-t |
|
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 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]-t |
|
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 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 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 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 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 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 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 = |
|
23 | development = True # change this to False to do a release | |
24 |
version_base = '0. |
|
24 | version_base = '0.10' | |
25 | branch = 'ipython' |
|
25 | branch = 'ipython' | |
26 |
revision = '11 |
|
26 | revision = '1163' | |
27 |
|
27 | |||
28 | if development: |
|
28 | if development: | |
29 | if branch == 'ipython': |
|
29 | if branch == 'ipython': |
@@ -292,17 +292,19 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 |
# |
|
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 |
|
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 if HAS_CTYPES: | |||||
321 | Term.cout.flush() |
|
323 | Term.cout.flush() | |
322 |
|
324 | |||
323 | else: |
|
325 | else: | |
324 |
def sigint_handler |
|
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 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 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 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 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 __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 |
|
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 __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 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 |
|
|
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 __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 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 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 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 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 |
|
|
273 | completion_string.append(symbol.ljust(max_len)) | |
279 | pos += 1 |
|
274 | pos += 1 | |
280 | else: |
|
275 | else: | |
281 |
|
|
276 | completion_string.append(symbol.rstrip() + '\n') | |
282 | pos = 1 |
|
277 | pos = 1 | |
283 |
self.write(''.join( |
|
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 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 |
|
|
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 __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 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 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 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 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 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 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 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 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 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 | |||||
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 __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 |
|
|
22 | from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase | |
22 |
|
|
23 | from IPython.frontend import frontendbase | |
23 |
|
|
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 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 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. |
|
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 |
""" |
|
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 |
|
|
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.history |
|
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 __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 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, |
|
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 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( |
|
74 | def my_func(): | |
57 |
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( |
|
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 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 |
|
103 | assert_equal(out_value, '1\n') | |
78 |
|
104 | |||
79 |
|
105 | |||
80 | @isolate_ipython0 |
|
106 | @isolate_ipython0 | |
@@ -87,20 +113,20 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 |
|
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 |
|
129 | yield assert_equal, out_value, '1\n' | |
104 |
|
130 | |||
105 |
|
131 | |||
106 | @isolate_ipython0 |
|
132 | @isolate_ipython0 | |
@@ -113,13 +139,13 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 |
|
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 |
|
148 | yield assert_equal, out_value, '1' | |
123 |
|
149 | |||
124 |
|
150 | |||
125 | @isolate_ipython0 |
|
151 | @isolate_ipython0 | |
@@ -129,10 +155,16 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 |
|
167 | assert_equal(out_value, 'Interactive namespace is empty.\n') | |
136 |
|
168 | |||
137 |
|
169 | |||
138 | @isolate_ipython0 |
|
170 | @isolate_ipython0 | |
@@ -156,8 +188,8 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 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 |
|
202 | yield assert_equal, out_value, '\nzzza zzzb ' | |
171 |
assert f.input_buffer |
|
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 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 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. |
|
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 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 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 _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 _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 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 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 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. |
|
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 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 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 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 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 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 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 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 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. |
|
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 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 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 |
|
|
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 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 __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. |
|
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 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 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 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 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 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 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 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 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_num |
|
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 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_num |
|
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 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_num |
|
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 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_num |
|
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 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 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 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 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 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. |
|
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 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. |
|
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 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 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 |
# |
|
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 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 |
|
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 the |
|
1516 | When scripts are executed via %run, we must keep a reference to the | |
1448 |
__main__ module (a FakeModule instance) around so |
|
1517 | namespace of their __main__ module (a FakeModule instance) around so | |
1449 |
clear it, rendering objects defined therein |
|
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 |
|
1524 | keep one copy of the namespace (the last one), thus preventing memory | |
1455 |
from old references while allowing the objects from the last |
|
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._ |
|
1549 | In [12]: IPython.__file__ in _ip.IP._main_ns_cache | |
1470 | Out[12]: True |
|
1550 | Out[12]: True | |
1471 | """ |
|
1551 | """ | |
1472 |
self. |
|
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 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._ |
|
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._ |
|
1571 | In [19]: len(_ip.IP._main_ns_cache) == 0 | |
1492 | Out[19]: True |
|
1572 | Out[19]: True | |
1493 | """ |
|
1573 | """ | |
1494 |
self. |
|
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 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 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 |
|
|
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 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, |
|
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 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 __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 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 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 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 __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 | |||||
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 |
|
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 | |||||
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 |
|
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 |
# Support |
|
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 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 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 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 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 |
|
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 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 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 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 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 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 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 | |||||
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 |
|
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 |
|
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 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 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 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 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 __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 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 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 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 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 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 | |||||
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 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 |
|
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 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 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 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 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 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 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 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 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 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 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. |
|
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 __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 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 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 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 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 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 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 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 __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 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 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 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 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 __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 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 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 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 |
|
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 | |||||
|
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 __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 |
|
|
30 | from IPython.kernel.controllerservice import ControllerService | |
31 |
|
|
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 __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 |
|
|
30 | from IPython.kernel import engineservice as es | |
31 | from IPython.kernel.enginefc import FCRemoteEngineRefFromService, IEngineBase |
|
31 | from IPython.testing.util import DeferredTestCase | |
32 |
|
|
32 | from IPython.kernel.controllerservice import IControllerBase | |
33 |
|
|
33 | from IPython.kernel.enginefc import FCRemoteEngineRefFromService, IEngineBase | |
34 |
|
34 | from IPython.kernel.engineservice import IEngineQueued | ||
35 |
|
|
35 | from IPython.kernel.engineconnector import EngineConnector | |
36 | IEngineCoreTestCase, \ |
|
36 | ||
37 | IEngineSerializedTestCase, \ |
|
37 | from IPython.kernel.tests.engineservicetest import \ | |
38 |
|
|
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 __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 |
|
|
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 |
|
|
35 | IEngineCoreTestCase, \ | |
36 |
|
|
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 | |||||
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 __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 | |||||
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 __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 |
|
|
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 __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 __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 |
|
|
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 |
|
|
30 | from IPython.kernel.multienginefc import IFCSynchronousMultiEngine | |
31 |
|
|
31 | from IPython.kernel.taskfc import IFCTaskController | |
32 |
|
|
32 | from IPython.kernel.util import printer | |
33 |
|
|
33 | from IPython.kernel.tests.tasktest import ITaskControllerTestCase | |
34 |
|
|
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 __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 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 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 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 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 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 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 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 |
|
|
126 | Flag to determine whether to skip test. If the condition is a | |
127 |
|
|
127 | callable, it is used at runtime to dynamically make the decision. This | |
128 |
|
|
128 | is useful for tests that may require costly imports, to delay the cost | |
129 |
|
|
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 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 | |||||
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 |
|
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 |
|
63 | EXCLUDE = [pjoin('IPython', 'external'), | |
36 | 'IPython/platutils_win32', |
|
64 | pjoin('IPython', 'frontend', 'process', 'winprocess.py'), | |
37 |
'IPython |
|
65 | pjoin('IPython_doctest_plugin'), | |
38 |
'IPython |
|
66 | pjoin('IPython', 'Gnuplot'), | |
39 | 'IPython/Gnuplot', |
|
67 | pjoin('IPython', 'Extensions', 'ipy_'), | |
40 |
'IPython |
|
68 | pjoin('IPython', 'Extensions', 'clearcmd'), | |
41 | 'IPython/Extensions/clearcmd', |
|
69 | pjoin('IPython', 'Extensions', 'PhysicalQInteractive'), | |
42 |
'IPython |
|
70 | pjoin('IPython', 'Extensions', 'scitedirector'), | |
43 |
'IPython |
|
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 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 |
|
|
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 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 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 | ||||
|
271 | print '*'*77 | |||
|
272 | print 'Ran %s test sets in %.3fs' % (nrunners, t_tests) | |||
|
273 | ||||
|
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 | ||||
|
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 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 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 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 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 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 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 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 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.doctest |
|
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 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 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 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 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 class C(object): | |||||
16 | self.name = name |
|
16 | self.name = name | |
17 |
|
17 | |||
18 | def __del__(self): |
|
18 | def __del__(self): | |
19 |
print ' |
|
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 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 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 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 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 | |||||
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 |
|
50 | for ns in IP.ns_refs_table: | |
13 |
if ns is |
|
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 | |||||
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 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 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 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 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 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 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 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 | |||||
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 | |||||
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 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. |
|
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