##// END OF EJS Templates
Clean up and document better starting process of global IPython in tests.
Fernando Perez -
Show More
@@ -1,160 +1,168 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
9 9 from __future__ import absolute_import
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Module imports
13 13 #-----------------------------------------------------------------------------
14 14
15 15 # From the standard library
16 16 import __builtin__
17 17 import commands
18 18 import new
19 19 import os
20 20 import sys
21 21
22 22 from . import tools
23 23 from IPython.utils.genutils import Term
24 24
25 25 #-----------------------------------------------------------------------------
26 26 # Functions
27 27 #-----------------------------------------------------------------------------
28 28
29 29 # Hack to modify the %run command so we can sync the user's namespace with the
30 30 # test globals. Once we move over to a clean magic system, this will be done
31 31 # with much less ugliness.
32 32
33 33 class py_file_finder(object):
34 34 def __init__(self,test_filename):
35 35 self.test_filename = test_filename
36 36
37 37 def __call__(self,name):
38 38 from IPython.utils.genutils import get_py_filename
39 39 try:
40 40 return get_py_filename(name)
41 41 except IOError:
42 42 test_dir = os.path.dirname(self.test_filename)
43 43 new_path = os.path.join(test_dir,name)
44 44 return get_py_filename(new_path)
45 45
46 46
47 47 def _run_ns_sync(self,arg_s,runner=None):
48 48 """Modified version of %run that syncs testing namespaces.
49 49
50 50 This is strictly needed for running doctests that call %run.
51 51 """
52 52 #print >> sys.stderr, 'in run_ns_sync', arg_s # dbg
53 53
54 54 _ip = get_ipython()
55 55 finder = py_file_finder(arg_s)
56 56 out = _ip.magic_run_ori(arg_s,runner,finder)
57 57 return out
58 58
59 59
60 60 class ipnsdict(dict):
61 61 """A special subclass of dict for use as an IPython namespace in doctests.
62 62
63 63 This subclass adds a simple checkpointing capability so that when testing
64 64 machinery clears it (we use it as the test execution context), it doesn't
65 65 get completely destroyed.
66 66 """
67 67
68 68 def __init__(self,*a):
69 69 dict.__init__(self,*a)
70 70 self._savedict = {}
71 71
72 72 def clear(self):
73 73 dict.clear(self)
74 74 self.update(self._savedict)
75
75
76 76 def _checkpoint(self):
77 77 self._savedict.clear()
78 78 self._savedict.update(self)
79 79
80 80 def update(self,other):
81 81 self._checkpoint()
82 82 dict.update(self,other)
83 83
84 84 # If '_' is in the namespace, python won't set it when executing code,
85 85 # and we have examples that test it. So we ensure that the namespace
86 86 # is always 'clean' of it before it's used for test code execution.
87 87 self.pop('_',None)
88 88
89 89 # The builtins namespace must *always* be the real __builtin__ module,
90 90 # else weird stuff happens. The main ipython code does have provisions
91 91 # to ensure this after %run, but since in this class we do some
92 92 # aggressive low-level cleaning of the execution namespace, we need to
93 93 # correct for that ourselves, to ensure consitency with the 'real'
94 94 # ipython.
95 95 self['__builtins__'] = __builtin__
96 96
97 97
98 98 def get_ipython():
99 99 # This will get replaced by the real thing once we start IPython below
100 100 return start_ipython()
101 101
102 102 def start_ipython():
103 103 """Start a global IPython shell, which we need for IPython-specific syntax.
104 104 """
105 105 global get_ipython
106 106
107 107 # This function should only ever run once!
108 108 if hasattr(start_ipython,'already_called'):
109 109 return
110 110 start_ipython.already_called = True
111 111
112 112 # Ok, first time we're called, go ahead
113 113 from IPython.core import ipapp, iplib
114 114
115 115 def xsys(cmd):
116 116 """Execute a command and print its output.
117 117
118 118 This is just a convenience function to replace the IPython system call
119 119 with one that is more doctest-friendly.
120 120 """
121 121 cmd = _ip.var_expand(cmd,depth=1)
122 122 sys.stdout.write(commands.getoutput(cmd))
123 123 sys.stdout.flush()
124 124
125 125 # Store certain global objects that IPython modifies
126 126 _displayhook = sys.displayhook
127 127 _excepthook = sys.excepthook
128 128 _main = sys.modules.get('__main__')
129 129
130 # Create custom argv and namespaces for our IPython to be test-friendly
130 131 argv = tools.default_argv()
132 user_ns, global_ns = iplib.make_user_namespaces(ipnsdict(), {})
131 133
132 # Start IPython instance. We customize it to start with minimal frills.
133 user_ns,global_ns = iplib.make_user_namespaces(ipnsdict(),{})
134 # Create and initialize our test-friendly IPython instance.
134 135 ip = ipapp.IPythonApp(argv, user_ns=user_ns, user_global_ns=global_ns)
135 136 ip.initialize()
137
138 # A few more tweaks needed for playing nicely with doctests...
139
140 # These traps are normally only active for interactive use, set them
141 # permanently since we'll be mocking interactive sessions.
136 142 ip.shell.builtin_trap.set()
137 143
138 144 # Set error printing to stdout so nose can doctest exceptions
139 145 ip.shell.InteractiveTB.out_stream = 'stdout'
140 146
147 # Modify the IPython system call with one that uses getoutput, so that we
148 # can capture subcommands and print them to Python's stdout, otherwise the
149 # doctest machinery would miss them.
150 ip.shell.system = xsys
151
152 # IPython is ready, now clean up some global state...
153
141 154 # Deactivate the various python system hooks added by ipython for
142 155 # interactive convenience so we don't confuse the doctest system
143 156 sys.modules['__main__'] = _main
144 157 sys.displayhook = _displayhook
145 158 sys.excepthook = _excepthook
146 159
147 160 # So that ipython magics and aliases can be doctested (they work by making
148 161 # a call into a global _ip object). Also make the top-level get_ipython
149 # now return this without calling here again
162 # now return this without recursively calling here again.
150 163 _ip = ip.shell
151 164 get_ipython = _ip.get_ipython
152 165 __builtin__._ip = _ip
153 166 __builtin__.get_ipython = get_ipython
154 167
155 # Modify the IPython system call with one that uses getoutput, so that we
156 # can capture subcommands and print them to Python's stdout, otherwise the
157 # doctest machinery would miss them.
158 ip.shell.system = xsys
159
160 168 return _ip
General Comments 0
You need to be logged in to leave comments. Login now