##// END OF EJS Templates
Add StreamProxy soft link io.stdout/err to sys.stdout/err...
MinRK -
Show More
@@ -1,195 +1,220 b''
1 """Global IPython app to support test running.
1 """Global IPython app to support test running.
2
2
3 We must start our own ipython object and heavily muck with it so that all the
3 We must start our own ipython object and heavily muck with it so that all the
4 modifications IPython makes to system behavior don't send the doctest machinery
4 modifications IPython makes to system behavior don't send the doctest machinery
5 into a fit. This code should be considered a gross hack, but it gets the job
5 into a fit. This code should be considered a gross hack, but it gets the job
6 done.
6 done.
7 """
7 """
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9 from __future__ import print_function
9 from __future__ import print_function
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2009-2010 The IPython Development Team
12 # Copyright (C) 2009-2010 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
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 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 # stdlib
22 # stdlib
23 import __builtin__
23 import __builtin__
24 import os
24 import os
25 import sys
25 import sys
26 from types import MethodType
26 from types import MethodType
27
27
28 # our own
28 # our own
29 from . import tools
29 from . import tools
30
30
31 from IPython.utils import io
31 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
32 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
32
33
33 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
34 # Functions
35 # Functions
35 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
36
37
38 class StreamProxy(io.IOStream):
39 """Proxy for sys.stdout/err. This will request the stream *at call time*
40 allowing for nose's Capture plugin's redirection of sys.stdout/err.
41
42 Parameters
43 ----------
44 name : str
45 The name of the stream. This will be requested anew at every call
46 """
47
48 def __init__(self, name):
49 self.name=name
50
51 @property
52 def stream(self):
53 return getattr(sys, self.name)
54
55 def flush(self):
56 self.stream.flush()
57
37 # Hack to modify the %run command so we can sync the user's namespace with the
58 # Hack to modify the %run command so we can sync the user's namespace with the
38 # test globals. Once we move over to a clean magic system, this will be done
59 # test globals. Once we move over to a clean magic system, this will be done
39 # with much less ugliness.
60 # with much less ugliness.
40
61
41 class py_file_finder(object):
62 class py_file_finder(object):
42 def __init__(self,test_filename):
63 def __init__(self,test_filename):
43 self.test_filename = test_filename
64 self.test_filename = test_filename
44
65
45 def __call__(self,name):
66 def __call__(self,name):
46 from IPython.utils.path import get_py_filename
67 from IPython.utils.path import get_py_filename
47 try:
68 try:
48 return get_py_filename(name)
69 return get_py_filename(name)
49 except IOError:
70 except IOError:
50 test_dir = os.path.dirname(self.test_filename)
71 test_dir = os.path.dirname(self.test_filename)
51 new_path = os.path.join(test_dir,name)
72 new_path = os.path.join(test_dir,name)
52 return get_py_filename(new_path)
73 return get_py_filename(new_path)
53
74
54
75
55 def _run_ns_sync(self,arg_s,runner=None):
76 def _run_ns_sync(self,arg_s,runner=None):
56 """Modified version of %run that syncs testing namespaces.
77 """Modified version of %run that syncs testing namespaces.
57
78
58 This is strictly needed for running doctests that call %run.
79 This is strictly needed for running doctests that call %run.
59 """
80 """
60 #print('in run_ns_sync', arg_s, file=sys.stderr) # dbg
81 #print('in run_ns_sync', arg_s, file=sys.stderr) # dbg
61 finder = py_file_finder(arg_s)
82 finder = py_file_finder(arg_s)
62 return get_ipython().magic_run_ori(arg_s, runner, finder)
83 return get_ipython().magic_run_ori(arg_s, runner, finder)
63
84
64
85
65 class ipnsdict(dict):
86 class ipnsdict(dict):
66 """A special subclass of dict for use as an IPython namespace in doctests.
87 """A special subclass of dict for use as an IPython namespace in doctests.
67
88
68 This subclass adds a simple checkpointing capability so that when testing
89 This subclass adds a simple checkpointing capability so that when testing
69 machinery clears it (we use it as the test execution context), it doesn't
90 machinery clears it (we use it as the test execution context), it doesn't
70 get completely destroyed.
91 get completely destroyed.
71
92
72 In addition, it can handle the presence of the '_' key in a special manner,
93 In addition, it can handle the presence of the '_' key in a special manner,
73 which is needed because of how Python's doctest machinery operates with
94 which is needed because of how Python's doctest machinery operates with
74 '_'. See constructor and :meth:`update` for details.
95 '_'. See constructor and :meth:`update` for details.
75 """
96 """
76
97
77 def __init__(self,*a):
98 def __init__(self,*a):
78 dict.__init__(self,*a)
99 dict.__init__(self,*a)
79 self._savedict = {}
100 self._savedict = {}
80 # If this flag is True, the .update() method will unconditionally
101 # If this flag is True, the .update() method will unconditionally
81 # remove a key named '_'. This is so that such a dict can be used as a
102 # remove a key named '_'. This is so that such a dict can be used as a
82 # namespace in doctests that call '_'.
103 # namespace in doctests that call '_'.
83 self.protect_underscore = False
104 self.protect_underscore = False
84
105
85 def clear(self):
106 def clear(self):
86 dict.clear(self)
107 dict.clear(self)
87 self.update(self._savedict)
108 self.update(self._savedict)
88
109
89 def _checkpoint(self):
110 def _checkpoint(self):
90 self._savedict.clear()
111 self._savedict.clear()
91 self._savedict.update(self)
112 self._savedict.update(self)
92
113
93 def update(self,other):
114 def update(self,other):
94 self._checkpoint()
115 self._checkpoint()
95 dict.update(self,other)
116 dict.update(self,other)
96
117
97 if self.protect_underscore:
118 if self.protect_underscore:
98 # If '_' is in the namespace, python won't set it when executing
119 # If '_' is in the namespace, python won't set it when executing
99 # code *in doctests*, and we have multiple doctests that use '_'.
120 # code *in doctests*, and we have multiple doctests that use '_'.
100 # So we ensure that the namespace is always 'clean' of it before
121 # So we ensure that the namespace is always 'clean' of it before
101 # it's used for test code execution.
122 # it's used for test code execution.
102 # This flag is only turned on by the doctest machinery, so that
123 # This flag is only turned on by the doctest machinery, so that
103 # normal test code can assume the _ key is updated like any other
124 # normal test code can assume the _ key is updated like any other
104 # key and can test for its presence after cell executions.
125 # key and can test for its presence after cell executions.
105 self.pop('_', None)
126 self.pop('_', None)
106
127
107 # The builtins namespace must *always* be the real __builtin__ module,
128 # The builtins namespace must *always* be the real __builtin__ module,
108 # else weird stuff happens. The main ipython code does have provisions
129 # else weird stuff happens. The main ipython code does have provisions
109 # to ensure this after %run, but since in this class we do some
130 # to ensure this after %run, but since in this class we do some
110 # aggressive low-level cleaning of the execution namespace, we need to
131 # aggressive low-level cleaning of the execution namespace, we need to
111 # correct for that ourselves, to ensure consitency with the 'real'
132 # correct for that ourselves, to ensure consitency with the 'real'
112 # ipython.
133 # ipython.
113 self['__builtins__'] = __builtin__
134 self['__builtins__'] = __builtin__
114
135
115
136
116 def get_ipython():
137 def get_ipython():
117 # This will get replaced by the real thing once we start IPython below
138 # This will get replaced by the real thing once we start IPython below
118 return start_ipython()
139 return start_ipython()
119
140
120
141
121 # A couple of methods to override those in the running IPython to interact
142 # A couple of methods to override those in the running IPython to interact
122 # better with doctest (doctest captures on raw stdout, so we need to direct
143 # better with doctest (doctest captures on raw stdout, so we need to direct
123 # various types of output there otherwise it will miss them).
144 # various types of output there otherwise it will miss them).
124
145
125 def xsys(self, cmd):
146 def xsys(self, cmd):
126 """Replace the default system call with a capturing one for doctest.
147 """Replace the default system call with a capturing one for doctest.
127 """
148 """
128 # We use getoutput, but we need to strip it because pexpect captures
149 # We use getoutput, but we need to strip it because pexpect captures
129 # the trailing newline differently from commands.getoutput
150 # the trailing newline differently from commands.getoutput
130 print(self.getoutput(cmd, split=False).rstrip(), end='', file=sys.stdout)
151 print(self.getoutput(cmd, split=False).rstrip(), end='', file=sys.stdout)
131 sys.stdout.flush()
152 sys.stdout.flush()
132
153
133
154
134 def _showtraceback(self, etype, evalue, stb):
155 def _showtraceback(self, etype, evalue, stb):
135 """Print the traceback purely on stdout for doctest to capture it.
156 """Print the traceback purely on stdout for doctest to capture it.
136 """
157 """
137 print(self.InteractiveTB.stb2text(stb), file=sys.stdout)
158 print(self.InteractiveTB.stb2text(stb), file=sys.stdout)
138
159
139
160
140 def start_ipython():
161 def start_ipython():
141 """Start a global IPython shell, which we need for IPython-specific syntax.
162 """Start a global IPython shell, which we need for IPython-specific syntax.
142 """
163 """
143 global get_ipython
164 global get_ipython
144
165
145 # This function should only ever run once!
166 # This function should only ever run once!
146 if hasattr(start_ipython, 'already_called'):
167 if hasattr(start_ipython, 'already_called'):
147 return
168 return
148 start_ipython.already_called = True
169 start_ipython.already_called = True
149
170
150 # Store certain global objects that IPython modifies
171 # Store certain global objects that IPython modifies
151 _displayhook = sys.displayhook
172 _displayhook = sys.displayhook
152 _excepthook = sys.excepthook
173 _excepthook = sys.excepthook
153 _main = sys.modules.get('__main__')
174 _main = sys.modules.get('__main__')
154
175
155 # Create custom argv and namespaces for our IPython to be test-friendly
176 # Create custom argv and namespaces for our IPython to be test-friendly
156 config = tools.default_config()
177 config = tools.default_config()
157
178
158 # Create and initialize our test-friendly IPython instance.
179 # Create and initialize our test-friendly IPython instance.
159 shell = TerminalInteractiveShell.instance(config=config,
180 shell = TerminalInteractiveShell.instance(config=config,
160 user_ns=ipnsdict(),
181 user_ns=ipnsdict(),
161 user_global_ns={}
182 user_global_ns={}
162 )
183 )
163
184
164 # A few more tweaks needed for playing nicely with doctests...
185 # A few more tweaks needed for playing nicely with doctests...
165
186
166 # These traps are normally only active for interactive use, set them
187 # These traps are normally only active for interactive use, set them
167 # permanently since we'll be mocking interactive sessions.
188 # permanently since we'll be mocking interactive sessions.
168 shell.builtin_trap.activate()
189 shell.builtin_trap.activate()
169
190
170 # Modify the IPython system call with one that uses getoutput, so that we
191 # Modify the IPython system call with one that uses getoutput, so that we
171 # can capture subcommands and print them to Python's stdout, otherwise the
192 # can capture subcommands and print them to Python's stdout, otherwise the
172 # doctest machinery would miss them.
193 # doctest machinery would miss them.
173 shell.system = MethodType(xsys, shell, TerminalInteractiveShell)
194 shell.system = MethodType(xsys, shell, TerminalInteractiveShell)
174
195
175
196
176 shell._showtraceback = MethodType(_showtraceback, shell,
197 shell._showtraceback = MethodType(_showtraceback, shell,
177 TerminalInteractiveShell)
198 TerminalInteractiveShell)
178
199
179 # IPython is ready, now clean up some global state...
200 # IPython is ready, now clean up some global state...
180
201
181 # Deactivate the various python system hooks added by ipython for
202 # Deactivate the various python system hooks added by ipython for
182 # interactive convenience so we don't confuse the doctest system
203 # interactive convenience so we don't confuse the doctest system
183 sys.modules['__main__'] = _main
204 sys.modules['__main__'] = _main
184 sys.displayhook = _displayhook
205 sys.displayhook = _displayhook
185 sys.excepthook = _excepthook
206 sys.excepthook = _excepthook
186
207
187 # So that ipython magics and aliases can be doctested (they work by making
208 # So that ipython magics and aliases can be doctested (they work by making
188 # a call into a global _ip object). Also make the top-level get_ipython
209 # a call into a global _ip object). Also make the top-level get_ipython
189 # now return this without recursively calling here again.
210 # now return this without recursively calling here again.
190 _ip = shell
211 _ip = shell
191 get_ipython = _ip.get_ipython
212 get_ipython = _ip.get_ipython
192 __builtin__._ip = _ip
213 __builtin__._ip = _ip
193 __builtin__.get_ipython = get_ipython
214 __builtin__.get_ipython = get_ipython
215
216 # To avoid extra IPython messages during testing, suppress io.stdout/stderr
217 io.stdout = StreamProxy('stdout')
218 io.stderr = StreamProxy('stderr')
194
219
195 return _ip
220 return _ip
General Comments 0
You need to be logged in to leave comments. Login now