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