##// END OF EJS Templates
Tweak code with suggestions from yesterday.
Thomas Kluyver -
Show More
@@ -1,375 +1,376 b''
1 1 # -*- coding: utf-8 -*-
2 2 # coding: utf-8
3 3 """A simple configuration system.
4 4
5 5 Authors
6 6 -------
7 7 * Brian Granger
8 8 * Fernando Perez
9 9 """
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Copyright (C) 2008-2009 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 import __builtin__
23 23 import os
24 24 import sys
25 25
26 26 from IPython.external import argparse
27 27 from IPython.utils.path import filefind
28 28
29 29 #-----------------------------------------------------------------------------
30 30 # Exceptions
31 31 #-----------------------------------------------------------------------------
32 32
33 33
34 34 class ConfigError(Exception):
35 35 pass
36 36
37 37
38 38 class ConfigLoaderError(ConfigError):
39 39 pass
40 40
41 41 #-----------------------------------------------------------------------------
42 42 # Argparse fix
43 43 #-----------------------------------------------------------------------------
44 44
45 45 # Unfortunately argparse by default prints help messages to stderr instead of
46 46 # stdout. This makes it annoying to capture long help screens at the command
47 47 # line, since one must know how to pipe stderr, which many users don't know how
48 48 # to do. So we override the print_help method with one that defaults to
49 49 # stdout and use our class instead.
50 50
51 51 class ArgumentParser(argparse.ArgumentParser):
52 52 """Simple argparse subclass that prints help to stdout by default."""
53 53
54 54 def print_help(self, file=None):
55 55 if file is None:
56 56 file = sys.stdout
57 57 return super(ArgumentParser, self).print_help(file)
58 58
59 59 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
60 60
61 61 #-----------------------------------------------------------------------------
62 62 # Config class for holding config information
63 63 #-----------------------------------------------------------------------------
64 64
65 65
66 66 class Config(dict):
67 67 """An attribute based dict that can do smart merges."""
68 68
69 69 def __init__(self, *args, **kwds):
70 70 dict.__init__(self, *args, **kwds)
71 71 # This sets self.__dict__ = self, but it has to be done this way
72 72 # because we are also overriding __setattr__.
73 73 dict.__setattr__(self, '__dict__', self)
74 74
75 75 def _merge(self, other):
76 76 to_update = {}
77 77 for k, v in other.iteritems():
78 78 if not self.has_key(k):
79 79 to_update[k] = v
80 80 else: # I have this key
81 81 if isinstance(v, Config):
82 82 # Recursively merge common sub Configs
83 83 self[k]._merge(v)
84 84 else:
85 85 # Plain updates for non-Configs
86 86 to_update[k] = v
87 87
88 88 self.update(to_update)
89 89
90 90 def _is_section_key(self, key):
91 91 if key[0].upper()==key[0] and not key.startswith('_'):
92 92 return True
93 93 else:
94 94 return False
95 95
96 96 def __contains__(self, key):
97 97 if self._is_section_key(key):
98 98 return True
99 99 else:
100 100 return super(Config, self).__contains__(key)
101 101 # .has_key is deprecated for dictionaries.
102 102 has_key = __contains__
103 103
104 104 def _has_section(self, key):
105 105 if self._is_section_key(key):
106 106 if super(Config, self).__contains__(key):
107 107 return True
108 108 return False
109 109
110 110 def copy(self):
111 111 return type(self)(dict.copy(self))
112 112
113 113 def __copy__(self):
114 114 return self.copy()
115 115
116 116 def __deepcopy__(self, memo):
117 117 import copy
118 118 return type(self)(copy.deepcopy(self.items()))
119 119
120 120 def __getitem__(self, key):
121 121 # Because we use this for an exec namespace, we need to delegate
122 122 # the lookup of names in __builtin__ to itself. This means
123 123 # that you can't have section or attribute names that are
124 124 # builtins.
125 125 try:
126 126 return getattr(__builtin__, key)
127 127 except AttributeError:
128 128 pass
129 129 if self._is_section_key(key):
130 130 try:
131 131 return dict.__getitem__(self, key)
132 132 except KeyError:
133 133 c = Config()
134 134 dict.__setitem__(self, key, c)
135 135 return c
136 136 else:
137 137 return dict.__getitem__(self, key)
138 138
139 139 def __setitem__(self, key, value):
140 140 # Don't allow names in __builtin__ to be modified.
141 141 if hasattr(__builtin__, key):
142 142 raise ConfigError('Config variable names cannot have the same name '
143 143 'as a Python builtin: %s' % key)
144 144 if self._is_section_key(key):
145 145 if not isinstance(value, Config):
146 146 raise ValueError('values whose keys begin with an uppercase '
147 147 'char must be Config instances: %r, %r' % (key, value))
148 148 else:
149 149 dict.__setitem__(self, key, value)
150 150
151 151 def __getattr__(self, key):
152 152 try:
153 153 return self.__getitem__(key)
154 154 except KeyError, e:
155 155 raise AttributeError(e)
156 156
157 157 def __setattr__(self, key, value):
158 158 try:
159 159 self.__setitem__(key, value)
160 160 except KeyError, e:
161 161 raise AttributeError(e)
162 162
163 163 def __delattr__(self, key):
164 164 try:
165 165 dict.__delitem__(self, key)
166 166 except KeyError, e:
167 167 raise AttributeError(e)
168 168
169 169
170 170 #-----------------------------------------------------------------------------
171 171 # Config loading classes
172 172 #-----------------------------------------------------------------------------
173 173
174 174
175 175 class ConfigLoader(object):
176 176 """A object for loading configurations from just about anywhere.
177 177
178 178 The resulting configuration is packaged as a :class:`Struct`.
179 179
180 180 Notes
181 181 -----
182 182 A :class:`ConfigLoader` does one thing: load a config from a source
183 183 (file, command line arguments) and returns the data as a :class:`Struct`.
184 184 There are lots of things that :class:`ConfigLoader` does not do. It does
185 185 not implement complex logic for finding config files. It does not handle
186 186 default values or merge multiple configs. These things need to be
187 187 handled elsewhere.
188 188 """
189 189
190 190 def __init__(self):
191 191 """A base class for config loaders.
192 192
193 193 Examples
194 194 --------
195 195
196 196 >>> cl = ConfigLoader()
197 197 >>> config = cl.load_config()
198 198 >>> config
199 199 {}
200 200 """
201 201 self.clear()
202 202
203 203 def clear(self):
204 204 self.config = Config()
205 205
206 206 def load_config(self):
207 207 """Load a config from somewhere, return a :class:`Config` instance.
208 208
209 209 Usually, this will cause self.config to be set and then returned.
210 210 However, in most cases, :meth:`ConfigLoader.clear` should be called
211 211 to erase any previous state.
212 212 """
213 213 self.clear()
214 214 return self.config
215 215
216 216
217 217 class FileConfigLoader(ConfigLoader):
218 218 """A base class for file based configurations.
219 219
220 220 As we add more file based config loaders, the common logic should go
221 221 here.
222 222 """
223 223 pass
224 224
225 225
226 226 class PyFileConfigLoader(FileConfigLoader):
227 227 """A config loader for pure python files.
228 228
229 229 This calls execfile on a plain python file and looks for attributes
230 230 that are all caps. These attribute are added to the config Struct.
231 231 """
232 232
233 233 def __init__(self, filename, path=None):
234 234 """Build a config loader for a filename and path.
235 235
236 236 Parameters
237 237 ----------
238 238 filename : str
239 239 The file name of the config file.
240 240 path : str, list, tuple
241 241 The path to search for the config file on, or a sequence of
242 242 paths to try in order.
243 243 """
244 244 super(PyFileConfigLoader, self).__init__()
245 245 self.filename = filename
246 246 self.path = path
247 247 self.full_filename = ''
248 248 self.data = None
249 249
250 250 def load_config(self):
251 251 """Load the config from a file and return it as a Struct."""
252 252 self.clear()
253 253 self._find_file()
254 254 self._read_file_as_dict()
255 255 self._convert_to_config()
256 256 return self.config
257 257
258 258 def _find_file(self):
259 259 """Try to find the file by searching the paths."""
260 260 self.full_filename = filefind(self.filename, self.path)
261 261
262 262 def _read_file_as_dict(self):
263 263 """Load the config file into self.config, with recursive loading."""
264 264 # This closure is made available in the namespace that is used
265 265 # to exec the config file. This allows users to call
266 266 # load_subconfig('myconfig.py') to load config files recursively.
267 267 # It needs to be a closure because it has references to self.path
268 268 # and self.config. The sub-config is loaded with the same path
269 269 # as the parent, but it uses an empty config which is then merged
270 270 # with the parents.
271 271 def load_subconfig(fname):
272 272 loader = PyFileConfigLoader(fname, self.path)
273 273 try:
274 274 sub_config = loader.load_config()
275 275 except IOError:
276 276 # Pass silently if the sub config is not there. This happens
277 277 # when a user us using a profile, but not the default config.
278 278 pass
279 279 else:
280 280 self.config._merge(sub_config)
281 281
282 282 # Again, this needs to be a closure and should be used in config
283 283 # files to get the config being loaded.
284 284 def get_config():
285 285 return self.config
286 286
287 287 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
288 conf_filename = self.full_filename.encode(sys.getfilesystemencoding())
288 fs_encoding = sys.getfilesystemencoding() or 'ascii'
289 conf_filename = self.full_filename.encode(fs_encoding)
289 290 execfile(conf_filename, namespace)
290 291
291 292 def _convert_to_config(self):
292 293 if self.data is None:
293 294 ConfigLoaderError('self.data does not exist')
294 295
295 296
296 297 class CommandLineConfigLoader(ConfigLoader):
297 298 """A config loader for command line arguments.
298 299
299 300 As we add more command line based loaders, the common logic should go
300 301 here.
301 302 """
302 303
303 304
304 305 class ArgParseConfigLoader(CommandLineConfigLoader):
305 306
306 307 def __init__(self, argv=None, *parser_args, **parser_kw):
307 308 """Create a config loader for use with argparse.
308 309
309 310 Parameters
310 311 ----------
311 312
312 313 argv : optional, list
313 314 If given, used to read command-line arguments from, otherwise
314 315 sys.argv[1:] is used.
315 316
316 317 parser_args : tuple
317 318 A tuple of positional arguments that will be passed to the
318 319 constructor of :class:`argparse.ArgumentParser`.
319 320
320 321 parser_kw : dict
321 322 A tuple of keyword arguments that will be passed to the
322 323 constructor of :class:`argparse.ArgumentParser`.
323 324 """
324 325 super(CommandLineConfigLoader, self).__init__()
325 326 if argv == None:
326 327 argv = sys.argv[1:]
327 328 self.argv = argv
328 329 self.parser_args = parser_args
329 330 self.version = parser_kw.pop("version", None)
330 331 kwargs = dict(argument_default=argparse.SUPPRESS)
331 332 kwargs.update(parser_kw)
332 333 self.parser_kw = kwargs
333 334
334 335 def load_config(self, args=None):
335 336 """Parse command line arguments and return as a Struct.
336 337
337 338 Parameters
338 339 ----------
339 340
340 341 args : optional, list
341 342 If given, a list with the structure of sys.argv[1:] to parse
342 343 arguments from. If not given, the instance's self.argv attribute
343 344 (given at construction time) is used."""
344 345 self.clear()
345 346 if args is None:
346 347 args = self.argv
347 348 self._create_parser()
348 349 self._parse_args(args)
349 350 self._convert_to_config()
350 351 return self.config
351 352
352 353 def get_extra_args(self):
353 354 if hasattr(self, 'extra_args'):
354 355 return self.extra_args
355 356 else:
356 357 return []
357 358
358 359 def _create_parser(self):
359 360 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
360 361 self._add_arguments()
361 362
362 363 def _add_arguments(self):
363 364 raise NotImplementedError("subclasses must implement _add_arguments")
364 365
365 366 def _parse_args(self, args):
366 367 """self.parser->self.parsed_data"""
367 368 self.parsed_data, self.extra_args = self.parser.parse_known_args(args)
368 369
369 370 def _convert_to_config(self):
370 371 """self.parsed_data->self.config"""
371 372 for k, v in vars(self.parsed_data).iteritems():
372 373 exec_str = 'self.config.' + k + '= v'
373 374 exec exec_str in locals(), globals()
374 375
375 376
@@ -1,461 +1,465 b''
1 1 # encoding: utf-8
2 2 """
3 3 An application for IPython.
4 4
5 5 All top-level applications should use the classes in this module for
6 6 handling configuration and creating componenets.
7 7
8 8 The job of an :class:`Application` is to create the master configuration
9 9 object and then create the configurable objects, passing the config to them.
10 10
11 11 Authors:
12 12
13 13 * Brian Granger
14 14 * Fernando Perez
15 15
16 16 Notes
17 17 -----
18 18 """
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Copyright (C) 2008-2009 The IPython Development Team
22 22 #
23 23 # Distributed under the terms of the BSD License. The full license is in
24 24 # the file COPYING, distributed as part of this software.
25 25 #-----------------------------------------------------------------------------
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Imports
29 29 #-----------------------------------------------------------------------------
30 30
31 31 import logging
32 32 import os
33 33 import sys
34 34
35 35 from IPython.core import release, crashhandler
36 36 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
37 37 from IPython.config.loader import (
38 38 PyFileConfigLoader,
39 39 ArgParseConfigLoader,
40 40 Config,
41 41 )
42 42
43 43 #-----------------------------------------------------------------------------
44 44 # Classes and functions
45 45 #-----------------------------------------------------------------------------
46 46
47 47 class ApplicationError(Exception):
48 48 pass
49 49
50 50
51 51 class BaseAppConfigLoader(ArgParseConfigLoader):
52 52 """Default command line options for IPython based applications."""
53 53
54 54 def _add_ipython_dir(self, parser):
55 55 """Add the --ipython-dir option to the parser."""
56 56 paa = parser.add_argument
57 57 paa('--ipython-dir',
58 58 dest='Global.ipython_dir',type=unicode,
59 59 help=
60 60 """Set to override default location of the IPython directory
61 61 IPYTHON_DIR, stored as Global.ipython_dir. This can also be
62 62 specified through the environment variable IPYTHON_DIR.""",
63 63 metavar='Global.ipython_dir')
64 64
65 65 def _add_log_level(self, parser):
66 66 """Add the --log-level option to the parser."""
67 67 paa = parser.add_argument
68 68 paa('--log-level',
69 69 dest="Global.log_level",type=int,
70 70 help='Set the log level (0,10,20,30,40,50). Default is 30.',
71 71 metavar='Global.log_level')
72 72
73 73 def _add_version(self, parser):
74 74 """Add the --version option to the parser."""
75 75 parser.add_argument('--version', action="version",
76 76 version=self.version)
77 77
78 78 def _add_arguments(self):
79 79 self._add_ipython_dir(self.parser)
80 80 self._add_log_level(self.parser)
81 81 self._add_version(self.parser)
82 82
83 83
84 84 class Application(object):
85 85 """Load a config, construct configurables and set them running.
86 86
87 87 The configuration of an application can be done via three different Config
88 88 objects, which are loaded and ultimately merged into a single one used
89 89 from that point on by the app. These are:
90 90
91 91 1. default_config: internal defaults, implemented in code.
92 92 2. file_config: read from the filesystem.
93 93 3. command_line_config: read from the system's command line flags.
94 94
95 95 During initialization, 3 is actually read before 2, since at the
96 96 command-line one may override the location of the file to be read. But the
97 97 above is the order in which the merge is made.
98 98 """
99 99
100 100 name = u'ipython'
101 101 description = 'IPython: an enhanced interactive Python shell.'
102 102 #: Usage message printed by argparse. If None, auto-generate
103 103 usage = None
104 104 #: The command line config loader. Subclass of ArgParseConfigLoader.
105 105 command_line_loader = BaseAppConfigLoader
106 106 #: The name of the config file to load, determined at runtime
107 107 config_file_name = None
108 108 #: The name of the default config file. Track separately from the actual
109 109 #: name because some logic happens only if we aren't using the default.
110 110 default_config_file_name = u'ipython_config.py'
111 111 default_log_level = logging.WARN
112 112 #: Set by --profile option
113 113 profile_name = None
114 114 #: User's ipython directory, typically ~/.ipython or ~/.config/ipython/
115 115 ipython_dir = None
116 116 #: Internal defaults, implemented in code.
117 117 default_config = None
118 118 #: Read from the filesystem.
119 119 file_config = None
120 120 #: Read from the system's command line flags.
121 121 command_line_config = None
122 122 #: The final config that will be passed to the main object.
123 123 master_config = None
124 124 #: A reference to the argv to be used (typically ends up being sys.argv[1:])
125 125 argv = None
126 126 #: extra arguments computed by the command-line loader
127 127 extra_args = None
128 128 #: The class to use as the crash handler.
129 129 crash_handler_class = crashhandler.CrashHandler
130 130
131 131 # Private attributes
132 132 _exiting = False
133 133 _initialized = False
134 134
135 135 def __init__(self, argv=None):
136 136 self.argv = sys.argv[1:] if argv is None else argv
137 137 self.init_logger()
138 138
139 139 def init_logger(self):
140 140 self.log = logging.getLogger(self.__class__.__name__)
141 141 # This is used as the default until the command line arguments are read.
142 142 self.log.setLevel(self.default_log_level)
143 143 self._log_handler = logging.StreamHandler()
144 144 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
145 145 self._log_handler.setFormatter(self._log_formatter)
146 146 self.log.addHandler(self._log_handler)
147 147
148 148 def _set_log_level(self, level):
149 149 self.log.setLevel(level)
150 150
151 151 def _get_log_level(self):
152 152 return self.log.level
153 153
154 154 log_level = property(_get_log_level, _set_log_level)
155 155
156 156 def initialize(self):
157 157 """Initialize the application.
158 158
159 159 Loads all configuration information and sets all application state, but
160 160 does not start any relevant processing (typically some kind of event
161 161 loop).
162 162
163 163 Once this method has been called, the application is flagged as
164 164 initialized and the method becomes a no-op."""
165 165
166 166 if self._initialized:
167 167 return
168 168
169 169 # The first part is protected with an 'attempt' wrapper, that will log
170 170 # failures with the basic system traceback machinery. Once our crash
171 171 # handler is in place, we can let any subsequent exception propagate,
172 172 # as our handler will log it with much better detail than the default.
173 173 self.attempt(self.create_crash_handler)
174 174
175 175 # Configuration phase
176 176 # Default config (internally hardwired in application code)
177 177 self.create_default_config()
178 178 self.log_default_config()
179 179 self.set_default_config_log_level()
180 180
181 181 # Command-line config
182 182 self.pre_load_command_line_config()
183 183 self.load_command_line_config()
184 184 self.set_command_line_config_log_level()
185 185 self.post_load_command_line_config()
186 186 self.log_command_line_config()
187 187
188 188 # Find resources needed for filesystem access, using information from
189 189 # the above two
190 190 self.find_ipython_dir()
191 191 self.find_resources()
192 192 self.find_config_file_name()
193 193 self.find_config_file_paths()
194 194
195 195 # File-based config
196 196 self.pre_load_file_config()
197 197 self.load_file_config()
198 198 self.set_file_config_log_level()
199 199 self.post_load_file_config()
200 200 self.log_file_config()
201 201
202 202 # Merge all config objects into a single one the app can then use
203 203 self.merge_configs()
204 204 self.log_master_config()
205 205
206 206 # Construction phase
207 207 self.pre_construct()
208 208 self.construct()
209 209 self.post_construct()
210 210
211 211 # Done, flag as such and
212 212 self._initialized = True
213 213
214 214 def start(self):
215 215 """Start the application."""
216 216 self.initialize()
217 217 self.start_app()
218 218
219 219 #-------------------------------------------------------------------------
220 220 # Various stages of Application creation
221 221 #-------------------------------------------------------------------------
222 222
223 223 def create_crash_handler(self):
224 224 """Create a crash handler, typically setting sys.excepthook to it."""
225 225 self.crash_handler = self.crash_handler_class(self)
226 226 sys.excepthook = self.crash_handler
227 227
228 228 def create_default_config(self):
229 229 """Create defaults that can't be set elsewhere.
230 230
231 231 For the most part, we try to set default in the class attributes
232 232 of Configurables. But, defaults the top-level Application (which is
233 233 not a HasTraits or Configurables) are not set in this way. Instead
234 234 we set them here. The Global section is for variables like this that
235 235 don't belong to a particular configurable.
236 236 """
237 237 c = Config()
238 238 c.Global.ipython_dir = get_ipython_dir()
239 239 c.Global.log_level = self.log_level
240 240 self.default_config = c
241 241
242 242 def log_default_config(self):
243 243 self.log.debug('Default config loaded:')
244 244 self.log.debug(repr(self.default_config))
245 245
246 246 def set_default_config_log_level(self):
247 247 try:
248 248 self.log_level = self.default_config.Global.log_level
249 249 except AttributeError:
250 250 # Fallback to the default_log_level class attribute
251 251 pass
252 252
253 253 def create_command_line_config(self):
254 254 """Create and return a command line config loader."""
255 255 return self.command_line_loader(
256 256 self.argv,
257 257 description=self.description,
258 258 version=release.version,
259 259 usage=self.usage
260 260 )
261 261
262 262 def pre_load_command_line_config(self):
263 263 """Do actions just before loading the command line config."""
264 264 pass
265 265
266 266 def load_command_line_config(self):
267 267 """Load the command line config."""
268 268 loader = self.create_command_line_config()
269 269 self.command_line_config = loader.load_config()
270 270 self.extra_args = loader.get_extra_args()
271 271
272 272 def set_command_line_config_log_level(self):
273 273 try:
274 274 self.log_level = self.command_line_config.Global.log_level
275 275 except AttributeError:
276 276 pass
277 277
278 278 def post_load_command_line_config(self):
279 279 """Do actions just after loading the command line config."""
280 280 pass
281 281
282 282 def log_command_line_config(self):
283 283 self.log.debug("Command line config loaded:")
284 284 self.log.debug(repr(self.command_line_config))
285 285
286 286 def find_ipython_dir(self):
287 287 """Set the IPython directory.
288 288
289 289 This sets ``self.ipython_dir``, but the actual value that is passed to
290 290 the application is kept in either ``self.default_config`` or
291 291 ``self.command_line_config``. This also adds ``self.ipython_dir`` to
292 292 ``sys.path`` so config files there can be referenced by other config
293 293 files.
294 294 """
295 295
296 296 try:
297 297 self.ipython_dir = self.command_line_config.Global.ipython_dir
298 298 except AttributeError:
299 299 self.ipython_dir = self.default_config.Global.ipython_dir
300 300 sys.path.append(os.path.abspath(self.ipython_dir))
301 301 if not os.path.isdir(self.ipython_dir):
302 302 os.makedirs(self.ipython_dir, mode=0777)
303 303 self.log.debug("IPYTHON_DIR set to: %s" % self.ipython_dir)
304 304
305 305 def find_resources(self):
306 306 """Find other resources that need to be in place.
307 307
308 308 Things like cluster directories need to be in place to find the
309 309 config file. These happen right after the IPython directory has
310 310 been set.
311 311 """
312 312 pass
313 313
314 314 def find_config_file_name(self):
315 315 """Find the config file name for this application.
316 316
317 317 This must set ``self.config_file_name`` to the filename of the
318 318 config file to use (just the filename). The search paths for the
319 319 config file are set in :meth:`find_config_file_paths` and then passed
320 320 to the config file loader where they are resolved to an absolute path.
321 321
322 322 If a profile has been set at the command line, this will resolve it.
323 323 """
324 324 try:
325 325 self.config_file_name = self.command_line_config.Global.config_file
326 326 except AttributeError:
327 327 pass
328 328 else:
329 329 return
330 330
331 331 try:
332 332 self.profile_name = self.command_line_config.Global.profile
333 333 except AttributeError:
334 334 # Just use the default as there is no profile
335 335 self.config_file_name = self.default_config_file_name
336 336 else:
337 337 # Use the default config file name and profile name if set
338 338 # to determine the used config file name.
339 339 name_parts = self.default_config_file_name.split('.')
340 340 name_parts.insert(1, u'_' + self.profile_name + u'.')
341 341 self.config_file_name = ''.join(name_parts)
342 342
343 343 def find_config_file_paths(self):
344 344 """Set the search paths for resolving the config file.
345 345
346 346 This must set ``self.config_file_paths`` to a sequence of search
347 347 paths to pass to the config file loader.
348 348 """
349 349 # Include our own profiles directory last, so that users can still find
350 350 # our shipped copies of builtin profiles even if they don't have them
351 351 # in their local ipython directory.
352 352 prof_dir = os.path.join(get_ipython_package_dir(), 'config', 'profile')
353 353 self.config_file_paths = (os.getcwdu(), self.ipython_dir, prof_dir)
354 354
355 355 def pre_load_file_config(self):
356 356 """Do actions before the config file is loaded."""
357 357 pass
358 358
359 359 def load_file_config(self, suppress_errors=True):
360 360 """Load the config file.
361 361
362 362 This tries to load the config file from disk. If successful, the
363 363 ``CONFIG_FILE`` config variable is set to the resolved config file
364 364 location. If not successful, an empty config is used.
365
366 By default, errors in loading config are handled, and a warning
367 printed on screen. For testing, the suppress_errors option is set
368 to False, so errors will make tests fail.
365 369 """
366 370 self.log.debug("Attempting to load config file: %s" %
367 371 self.config_file_name)
368 372 loader = PyFileConfigLoader(self.config_file_name,
369 373 path=self.config_file_paths)
370 374 try:
371 375 self.file_config = loader.load_config()
372 376 self.file_config.Global.config_file = loader.full_filename
373 377 except IOError:
374 378 # Only warn if the default config file was NOT being used.
375 379 if not self.config_file_name==self.default_config_file_name:
376 380 self.log.warn("Config file not found, skipping: %s" %
377 381 self.config_file_name, exc_info=True)
378 382 self.file_config = Config()
379 383 except:
380 384 if not suppress_errors: # For testing purposes
381 385 raise
382 386 self.log.warn("Error loading config file: %s" %
383 387 self.config_file_name, exc_info=True)
384 388 self.file_config = Config()
385 389
386 390 def set_file_config_log_level(self):
387 391 # We need to keeep self.log_level updated. But we only use the value
388 392 # of the file_config if a value was not specified at the command
389 393 # line, because the command line overrides everything.
390 394 if not hasattr(self.command_line_config.Global, 'log_level'):
391 395 try:
392 396 self.log_level = self.file_config.Global.log_level
393 397 except AttributeError:
394 398 pass # Use existing value
395 399
396 400 def post_load_file_config(self):
397 401 """Do actions after the config file is loaded."""
398 402 pass
399 403
400 404 def log_file_config(self):
401 405 if hasattr(self.file_config.Global, 'config_file'):
402 406 self.log.debug("Config file loaded: %s" %
403 407 self.file_config.Global.config_file)
404 408 self.log.debug(repr(self.file_config))
405 409
406 410 def merge_configs(self):
407 411 """Merge the default, command line and file config objects."""
408 412 config = Config()
409 413 config._merge(self.default_config)
410 414 config._merge(self.file_config)
411 415 config._merge(self.command_line_config)
412 416
413 417 # XXX fperez - propose to Brian we rename master_config to simply
414 418 # config, I think this is going to be heavily used in examples and
415 419 # application code and the name is shorter/easier to find/remember.
416 420 # For now, just alias it...
417 421 self.master_config = config
418 422 self.config = config
419 423
420 424 def log_master_config(self):
421 425 self.log.debug("Master config created:")
422 426 self.log.debug(repr(self.master_config))
423 427
424 428 def pre_construct(self):
425 429 """Do actions after the config has been built, but before construct."""
426 430 pass
427 431
428 432 def construct(self):
429 433 """Construct the main objects that make up this app."""
430 434 self.log.debug("Constructing main objects for application")
431 435
432 436 def post_construct(self):
433 437 """Do actions after construct, but before starting the app."""
434 438 pass
435 439
436 440 def start_app(self):
437 441 """Actually start the app."""
438 442 self.log.debug("Starting application")
439 443
440 444 #-------------------------------------------------------------------------
441 445 # Utility methods
442 446 #-------------------------------------------------------------------------
443 447
444 448 def exit(self, exit_status=0):
445 449 if self._exiting:
446 450 pass
447 451 else:
448 452 self.log.debug("Exiting application: %s" % self.name)
449 453 self._exiting = True
450 454 sys.exit(exit_status)
451 455
452 456 def attempt(self, func):
453 457 try:
454 458 func()
455 459 except SystemExit:
456 460 raise
457 461 except:
458 462 self.log.critical("Aborting application: %s" % self.name,
459 463 exc_info=True)
460 464 self.exit(0)
461 465
@@ -1,71 +1,74 b''
1 1 # coding: utf-8
2 2 """Tests for the compilerop module.
3 3 """
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (C) 2010 The IPython Development Team.
6 6 #
7 7 # Distributed under the terms of the BSD License.
8 8 #
9 9 # The full license is in the file COPYING.txt, distributed with this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15 from __future__ import print_function
16 16
17 17 # Stdlib imports
18 18 import linecache
19 import sys
19 20
20 21 # Third-party imports
21 22 import nose.tools as nt
22 23
23 24 # Our own imports
24 25 from IPython.core import compilerop
25 26
26 27 #-----------------------------------------------------------------------------
27 28 # Test functions
28 29 #-----------------------------------------------------------------------------
29 30
30 31 def test_code_name():
31 32 code = 'x=1'
32 33 name = compilerop.code_name(code)
33 34 nt.assert_true(name.startswith('<ipython-input-0'))
34 35
35 36
36 37 def test_code_name2():
37 38 code = 'x=1'
38 39 name = compilerop.code_name(code, 9)
39 40 nt.assert_true(name.startswith('<ipython-input-9'))
40 41
41 42
42 43 def test_compiler():
43 44 """Test the compiler correctly compiles and caches inputs
44 45 """
45 46 cp = compilerop.CachingCompiler()
46 47 ncache = len(linecache.cache)
47 48 cp('x=1', 'single')
48 49 nt.assert_true(len(linecache.cache) > ncache)
49 50
50 def test_compiler_unicode():
51 import sys
51 def setUp():
52 # Check we're in a proper Python 2 environment (some imports, such
53 # as GTK, can change the default encoding, which can hide bugs.)
52 54 nt.assert_equal(sys.getdefaultencoding(), "ascii")
53
55
56 def test_compiler_unicode():
54 57 cp = compilerop.CachingCompiler()
55 58 ncache = len(linecache.cache)
56 59 cp(u"t = 'žćčőđ'", "single")
57 60 nt.assert_true(len(linecache.cache) > ncache)
58 61
59 62 def test_compiler_check_cache():
60 63 """Test the compiler properly manages the cache.
61 64 """
62 65 # Rather simple-minded tests that just exercise the API
63 66 cp = compilerop.CachingCompiler()
64 67 cp('x=1', 'single', 99)
65 68 # Ensure now that after clearing the cache, our entries survive
66 69 cp.check_cache()
67 70 for k in linecache.cache:
68 71 if k.startswith('<ipython-input-99'):
69 72 break
70 73 else:
71 74 raise AssertionError('Entry for input-99 missing from linecache')
@@ -1,117 +1,119 b''
1 1 # coding: utf-8
2 2 """Tests for the IPython tab-completion machinery.
3 3 """
4 4 #-----------------------------------------------------------------------------
5 5 # Module imports
6 6 #-----------------------------------------------------------------------------
7 7
8 8 # stdlib
9 9 import os
10 10 import sys
11 11 import unittest
12 12
13 13 # third party
14 14 import nose.tools as nt
15 15
16 16 # our own packages
17 17 from IPython.utils.tempdir import TemporaryDirectory
18 18 from IPython.core.history import HistoryManager, extract_hist_ranges
19 19
20 def test_history():
20 def setUp():
21 21 nt.assert_equal(sys.getdefaultencoding(), "ascii")
22
23 def test_history():
22 24 ip = get_ipython()
23 25 with TemporaryDirectory() as tmpdir:
24 26 #tmpdir = '/software/temp'
25 27 histfile = os.path.realpath(os.path.join(tmpdir, 'history.sqlite'))
26 28 # Ensure that we restore the history management that we mess with in
27 29 # this test doesn't affect the IPython instance used by the test suite
28 30 # beyond this test.
29 31 hist_manager_ori = ip.history_manager
30 32 try:
31 33 ip.history_manager = HistoryManager(shell=ip)
32 34 ip.history_manager.hist_file = histfile
33 35 ip.history_manager.init_db() # Has to be called after changing file
34 36 ip.history_manager.reset()
35 37 print 'test',histfile
36 38 hist = ['a=1', 'def f():\n test = 1\n return test', u"b='β‚¬Γ†ΒΎΓ·ΓŸ'"]
37 39 for i, h in enumerate(hist, start=1):
38 40 ip.history_manager.store_inputs(i, h)
39 41
40 42 ip.history_manager.db_log_output = True
41 43 # Doesn't match the input, but we'll just check it's stored.
42 44 ip.history_manager.output_hist_reprs[3].append("spam")
43 45 ip.history_manager.store_output(3)
44 46
45 47 nt.assert_equal(ip.history_manager.input_hist_raw, [''] + hist)
46 48
47 49 # Check lines were written to DB
48 50 c = ip.history_manager.db.execute("SELECT source_raw FROM history")
49 51 nt.assert_equal([x for x, in c], hist)
50 52
51 53 # New session
52 54 ip.history_manager.reset()
53 55 newcmds = ["z=5","class X(object):\n pass", "k='p'"]
54 56 for i, cmd in enumerate(newcmds, start=1):
55 57 ip.history_manager.store_inputs(i, cmd)
56 58 gothist = ip.history_manager.get_range(start=1, stop=4)
57 59 nt.assert_equal(list(gothist), zip([0,0,0],[1,2,3], newcmds))
58 60 # Previous session:
59 61 gothist = ip.history_manager.get_range(-1, 1, 4)
60 62 nt.assert_equal(list(gothist), zip([1,1,1],[1,2,3], hist))
61 63
62 64 # Check get_hist_tail
63 65 gothist = ip.history_manager.get_tail(4, output=True,
64 66 include_latest=True)
65 67 expected = [(1, 3, (hist[-1], ["spam"])),
66 68 (2, 1, (newcmds[0], None)),
67 69 (2, 2, (newcmds[1], None)),
68 70 (2, 3, (newcmds[2], None)),]
69 71 nt.assert_equal(list(gothist), expected)
70 72
71 73 gothist = ip.history_manager.get_tail(2)
72 74 expected = [(2, 1, newcmds[0]),
73 75 (2, 2, newcmds[1])]
74 76 nt.assert_equal(list(gothist), expected)
75 77
76 78 # Check get_hist_search
77 79 gothist = ip.history_manager.search("*test*")
78 80 nt.assert_equal(list(gothist), [(1,2,hist[1])] )
79 81 gothist = ip.history_manager.search("b*", output=True)
80 82 nt.assert_equal(list(gothist), [(1,3,(hist[2],["spam"]))] )
81 83
82 84 # Cross testing: check that magic %save can get previous session.
83 85 testfilename = os.path.realpath(os.path.join(tmpdir, "test.py"))
84 86 ip.magic_save(testfilename + " ~1/1-3")
85 87 testfile = open(testfilename, "r")
86 88 nt.assert_equal(testfile.read().decode("utf-8"),
87 89 "# coding: utf-8\n" + "\n".join(hist))
88 90
89 91 # Duplicate line numbers - check that it doesn't crash, and
90 92 # gets a new session
91 93 ip.history_manager.store_inputs(1, "rogue")
92 94 nt.assert_equal(ip.history_manager.session_number, 3)
93 95 finally:
94 96 # Restore history manager
95 97 ip.history_manager = hist_manager_ori
96 98
97 99
98 100 def test_extract_hist_ranges():
99 101 instr = "1 2/3 ~4/5-6 ~4/7-~4/9 ~9/2-~7/5"
100 102 expected = [(0, 1, 2), # 0 == current session
101 103 (2, 3, 4),
102 104 (-4, 5, 7),
103 105 (-4, 7, 10),
104 106 (-9, 2, None), # None == to end
105 107 (-8, 1, None),
106 108 (-7, 1, 6)]
107 109 actual = list(extract_hist_ranges(instr))
108 110 nt.assert_equal(actual, expected)
109 111
110 112 def test_magic_rerun():
111 113 """Simple test for %rerun (no args -> rerun last line)"""
112 114 ip = get_ipython()
113 115 ip.run_cell("a = 10")
114 116 ip.run_cell("a += 1")
115 117 nt.assert_equal(ip.user_ns["a"], 11)
116 118 ip.run_cell("%rerun")
117 119 nt.assert_equal(ip.user_ns["a"], 12)
@@ -1,437 +1,441 b''
1 1 # -*- coding: utf-8 -*-
2 2 """IPython Test Suite Runner.
3 3
4 4 This module provides a main entry point to a user script to test IPython
5 5 itself from the command line. There are two ways of running this script:
6 6
7 7 1. With the syntax `iptest all`. This runs our entire test suite by
8 8 calling this script (with different arguments) or trial recursively. This
9 9 causes modules and package to be tested in different processes, using nose
10 10 or trial where appropriate.
11 11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 12 the script simply calls nose, but with special command line flags and
13 13 plugins loaded.
14 14
15 15 For now, this script requires that both nose and twisted are installed. This
16 16 will change in the future.
17 17 """
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Copyright (C) 2009 The IPython Development Team
21 21 #
22 22 # Distributed under the terms of the BSD License. The full license is in
23 23 # the file COPYING, distributed as part of this software.
24 24 #-----------------------------------------------------------------------------
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Imports
28 28 #-----------------------------------------------------------------------------
29 29
30 30 # Stdlib
31 31 import os
32 32 import os.path as path
33 33 import signal
34 34 import sys
35 35 import subprocess
36 36 import tempfile
37 37 import time
38 38 import warnings
39 39
40 40 # Note: monkeypatch!
41 41 # We need to monkeypatch a small problem in nose itself first, before importing
42 42 # it for actual use. This should get into nose upstream, but its release cycle
43 43 # is slow and we need it for our parametric tests to work correctly.
44 44 from IPython.testing import nosepatch
45 45 # Now, proceed to import nose itself
46 46 import nose.plugins.builtin
47 47 from nose.core import TestProgram
48 48
49 49 # Our own imports
50 50 from IPython.utils.path import get_ipython_module_path
51 51 from IPython.utils.process import find_cmd, pycmd2argv
52 52 from IPython.utils.sysinfo import sys_info
53 53
54 54 from IPython.testing import globalipapp
55 55 from IPython.testing.plugin.ipdoctest import IPythonDoctest
56 56
57 57 pjoin = path.join
58 58
59 59
60 60 #-----------------------------------------------------------------------------
61 61 # Globals
62 62 #-----------------------------------------------------------------------------
63 63
64 64
65 65 #-----------------------------------------------------------------------------
66 66 # Warnings control
67 67 #-----------------------------------------------------------------------------
68 68
69 69 # Twisted generates annoying warnings with Python 2.6, as will do other code
70 70 # that imports 'sets' as of today
71 71 warnings.filterwarnings('ignore', 'the sets module is deprecated',
72 72 DeprecationWarning )
73 73
74 74 # This one also comes from Twisted
75 75 warnings.filterwarnings('ignore', 'the sha module is deprecated',
76 76 DeprecationWarning)
77 77
78 78 # Wx on Fedora11 spits these out
79 79 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
80 80 UserWarning)
81 81
82 82 #-----------------------------------------------------------------------------
83 83 # Logic for skipping doctests
84 84 #-----------------------------------------------------------------------------
85 85
86 86 def test_for(mod):
87 87 """Test to see if mod is importable."""
88 88 try:
89 89 __import__(mod)
90 90 except (ImportError, RuntimeError):
91 91 # GTK reports Runtime error if it can't be initialized even if it's
92 92 # importable.
93 93 return False
94 94 else:
95 95 return True
96 96
97 97 # Global dict where we can store information on what we have and what we don't
98 98 # have available at test run time
99 99 have = {}
100 100
101 101 have['curses'] = test_for('_curses')
102 102 have['wx'] = test_for('wx')
103 103 have['wx.aui'] = test_for('wx.aui')
104 104 have['zope.interface'] = test_for('zope.interface')
105 105 have['twisted'] = test_for('twisted')
106 106 have['foolscap'] = test_for('foolscap')
107 107 have['pexpect'] = test_for('pexpect')
108 108
109 109 #-----------------------------------------------------------------------------
110 110 # Functions and classes
111 111 #-----------------------------------------------------------------------------
112 112
113 113 def report():
114 114 """Return a string with a summary report of test-related variables."""
115 115
116 116 out = [ sys_info(), '\n']
117 117
118 118 avail = []
119 119 not_avail = []
120 120
121 121 for k, is_avail in have.items():
122 122 if is_avail:
123 123 avail.append(k)
124 124 else:
125 125 not_avail.append(k)
126 126
127 127 if avail:
128 128 out.append('\nTools and libraries available at test time:\n')
129 129 avail.sort()
130 130 out.append(' ' + ' '.join(avail)+'\n')
131 131
132 132 if not_avail:
133 133 out.append('\nTools and libraries NOT available at test time:\n')
134 134 not_avail.sort()
135 135 out.append(' ' + ' '.join(not_avail)+'\n')
136 136
137 137 return ''.join(out)
138 138
139 139
140 140 def make_exclude():
141 141 """Make patterns of modules and packages to exclude from testing.
142 142
143 143 For the IPythonDoctest plugin, we need to exclude certain patterns that
144 144 cause testing problems. We should strive to minimize the number of
145 145 skipped modules, since this means untested code.
146 146
147 147 These modules and packages will NOT get scanned by nose at all for tests.
148 148 """
149 149 # Simple utility to make IPython paths more readably, we need a lot of
150 150 # these below
151 151 ipjoin = lambda *paths: pjoin('IPython', *paths)
152 152
153 153 exclusions = [ipjoin('external'),
154 154 pjoin('IPython_doctest_plugin'),
155 155 ipjoin('quarantine'),
156 156 ipjoin('deathrow'),
157 157 ipjoin('testing', 'attic'),
158 158 # This guy is probably attic material
159 159 ipjoin('testing', 'mkdoctests'),
160 160 # Testing inputhook will need a lot of thought, to figure out
161 161 # how to have tests that don't lock up with the gui event
162 162 # loops in the picture
163 163 ipjoin('lib', 'inputhook'),
164 164 # Config files aren't really importable stand-alone
165 165 ipjoin('config', 'default'),
166 166 ipjoin('config', 'profile'),
167 167 ]
168 168
169 169 if not have['wx']:
170 170 exclusions.append(ipjoin('lib', 'inputhookwx'))
171
172 # We do this unconditionally, so that the test suite doesn't import
173 # gtk, changing the default encoding and masking some unicode bugs.
174 exclusions.append(ipjoin('lib', 'inputhookgtk'))
171 175
172 176 # These have to be skipped on win32 because the use echo, rm, cd, etc.
173 177 # See ticket https://bugs.launchpad.net/bugs/366982
174 178 if sys.platform == 'win32':
175 179 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
176 180 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
177 181
178 182 if not have['pexpect']:
179 183 exclusions.extend([ipjoin('scripts', 'irunner'),
180 184 ipjoin('lib', 'irunner')])
181 185
182 186 # This is scary. We still have things in frontend and testing that
183 187 # are being tested by nose that use twisted. We need to rethink
184 188 # how we are isolating dependencies in testing.
185 189 if not (have['twisted'] and have['zope.interface'] and have['foolscap']):
186 190 exclusions.extend(
187 191 [ipjoin('testing', 'parametric'),
188 192 ipjoin('testing', 'util'),
189 193 ipjoin('testing', 'tests', 'test_decorators_trial'),
190 194 ] )
191 195
192 196 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
193 197 if sys.platform == 'win32':
194 198 exclusions = [s.replace('\\','\\\\') for s in exclusions]
195 199
196 200 return exclusions
197 201
198 202
199 203 class IPTester(object):
200 204 """Call that calls iptest or trial in a subprocess.
201 205 """
202 206 #: string, name of test runner that will be called
203 207 runner = None
204 208 #: list, parameters for test runner
205 209 params = None
206 210 #: list, arguments of system call to be made to call test runner
207 211 call_args = None
208 212 #: list, process ids of subprocesses we start (for cleanup)
209 213 pids = None
210 214
211 215 def __init__(self, runner='iptest', params=None):
212 216 """Create new test runner."""
213 217 p = os.path
214 218 if runner == 'iptest':
215 219 iptest_app = get_ipython_module_path('IPython.testing.iptest')
216 220 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
217 221 elif runner == 'trial':
218 222 # For trial, it needs to be installed system-wide
219 223 self.runner = pycmd2argv(p.abspath(find_cmd('trial')))
220 224 else:
221 225 raise Exception('Not a valid test runner: %s' % repr(runner))
222 226 if params is None:
223 227 params = []
224 228 if isinstance(params, str):
225 229 params = [params]
226 230 self.params = params
227 231
228 232 # Assemble call
229 233 self.call_args = self.runner+self.params
230 234
231 235 # Store pids of anything we start to clean up on deletion, if possible
232 236 # (on posix only, since win32 has no os.kill)
233 237 self.pids = []
234 238
235 239 if sys.platform == 'win32':
236 240 def _run_cmd(self):
237 241 # On Windows, use os.system instead of subprocess.call, because I
238 242 # was having problems with subprocess and I just don't know enough
239 243 # about win32 to debug this reliably. Os.system may be the 'old
240 244 # fashioned' way to do it, but it works just fine. If someone
241 245 # later can clean this up that's fine, as long as the tests run
242 246 # reliably in win32.
243 247 # What types of problems are you having. They may be related to
244 248 # running Python in unboffered mode. BG.
245 249 return os.system(' '.join(self.call_args))
246 250 else:
247 251 def _run_cmd(self):
248 252 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
249 253 subp = subprocess.Popen(self.call_args)
250 254 self.pids.append(subp.pid)
251 255 # If this fails, the pid will be left in self.pids and cleaned up
252 256 # later, but if the wait call succeeds, then we can clear the
253 257 # stored pid.
254 258 retcode = subp.wait()
255 259 self.pids.pop()
256 260 return retcode
257 261
258 262 def run(self):
259 263 """Run the stored commands"""
260 264 try:
261 265 return self._run_cmd()
262 266 except:
263 267 import traceback
264 268 traceback.print_exc()
265 269 return 1 # signal failure
266 270
267 271 def __del__(self):
268 272 """Cleanup on exit by killing any leftover processes."""
269 273
270 274 if not hasattr(os, 'kill'):
271 275 return
272 276
273 277 for pid in self.pids:
274 278 try:
275 279 print 'Cleaning stale PID:', pid
276 280 os.kill(pid, signal.SIGKILL)
277 281 except OSError:
278 282 # This is just a best effort, if we fail or the process was
279 283 # really gone, ignore it.
280 284 pass
281 285
282 286
283 287 def make_runners():
284 288 """Define the top-level packages that need to be tested.
285 289 """
286 290
287 291 # Packages to be tested via nose, that only depend on the stdlib
288 292 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
289 293 'scripts', 'testing', 'utils' ]
290 294 # The machinery in kernel needs twisted for real testing
291 295 trial_pkg_names = []
292 296
293 297 # And add twisted ones if conditions are met
294 298 if have['zope.interface'] and have['twisted'] and have['foolscap']:
295 299 # We only list IPython.kernel for testing using twisted.trial as
296 300 # nose and twisted.trial have conflicts that make the testing system
297 301 # unstable.
298 302 trial_pkg_names.append('kernel')
299 303
300 304 # For debugging this code, only load quick stuff
301 305 #nose_pkg_names = ['core', 'extensions'] # dbg
302 306 #trial_pkg_names = [] # dbg
303 307
304 308 # Make fully qualified package names prepending 'IPython.' to our name lists
305 309 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
306 310 trial_packages = ['IPython.%s' % m for m in trial_pkg_names ]
307 311
308 312 # Make runners
309 313 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
310 314 runners.extend([ (v, IPTester('trial', params=v)) for v in trial_packages ])
311 315
312 316 return runners
313 317
314 318
315 319 def run_iptest():
316 320 """Run the IPython test suite using nose.
317 321
318 322 This function is called when this script is **not** called with the form
319 323 `iptest all`. It simply calls nose with appropriate command line flags
320 324 and accepts all of the standard nose arguments.
321 325 """
322 326
323 327 warnings.filterwarnings('ignore',
324 328 'This will be removed soon. Use IPython.testing.util instead')
325 329
326 330 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
327 331
328 332 # Loading ipdoctest causes problems with Twisted, but
329 333 # our test suite runner now separates things and runs
330 334 # all Twisted tests with trial.
331 335 '--with-ipdoctest',
332 336 '--ipdoctest-tests','--ipdoctest-extension=txt',
333 337
334 338 # We add --exe because of setuptools' imbecility (it
335 339 # blindly does chmod +x on ALL files). Nose does the
336 340 # right thing and it tries to avoid executables,
337 341 # setuptools unfortunately forces our hand here. This
338 342 # has been discussed on the distutils list and the
339 343 # setuptools devs refuse to fix this problem!
340 344 '--exe',
341 345 ]
342 346
343 347 if nose.__version__ >= '0.11':
344 348 # I don't fully understand why we need this one, but depending on what
345 349 # directory the test suite is run from, if we don't give it, 0 tests
346 350 # get run. Specifically, if the test suite is run from the source dir
347 351 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
348 352 # even if the same call done in this directory works fine). It appears
349 353 # that if the requested package is in the current dir, nose bails early
350 354 # by default. Since it's otherwise harmless, leave it in by default
351 355 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
352 356 argv.append('--traverse-namespace')
353 357
354 358 # Construct list of plugins, omitting the existing doctest plugin, which
355 359 # ours replaces (and extends).
356 360 plugins = [IPythonDoctest(make_exclude())]
357 361 for p in nose.plugins.builtin.plugins:
358 362 plug = p()
359 363 if plug.name == 'doctest':
360 364 continue
361 365 plugins.append(plug)
362 366
363 367 # We need a global ipython running in this process
364 368 globalipapp.start_ipython()
365 369 # Now nose can run
366 370 TestProgram(argv=argv, plugins=plugins)
367 371
368 372
369 373 def run_iptestall():
370 374 """Run the entire IPython test suite by calling nose and trial.
371 375
372 376 This function constructs :class:`IPTester` instances for all IPython
373 377 modules and package and then runs each of them. This causes the modules
374 378 and packages of IPython to be tested each in their own subprocess using
375 379 nose or twisted.trial appropriately.
376 380 """
377 381
378 382 runners = make_runners()
379 383
380 384 # Run the test runners in a temporary dir so we can nuke it when finished
381 385 # to clean up any junk files left over by accident. This also makes it
382 386 # robust against being run in non-writeable directories by mistake, as the
383 387 # temp dir will always be user-writeable.
384 388 curdir = os.getcwd()
385 389 testdir = tempfile.gettempdir()
386 390 os.chdir(testdir)
387 391
388 392 # Run all test runners, tracking execution time
389 393 failed = []
390 394 t_start = time.time()
391 395 try:
392 396 for (name, runner) in runners:
393 397 print '*'*70
394 398 print 'IPython test group:',name
395 399 res = runner.run()
396 400 if res:
397 401 failed.append( (name, runner) )
398 402 finally:
399 403 os.chdir(curdir)
400 404 t_end = time.time()
401 405 t_tests = t_end - t_start
402 406 nrunners = len(runners)
403 407 nfail = len(failed)
404 408 # summarize results
405 409 print
406 410 print '*'*70
407 411 print 'Test suite completed for system with the following information:'
408 412 print report()
409 413 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
410 414 print
411 415 print 'Status:'
412 416 if not failed:
413 417 print 'OK'
414 418 else:
415 419 # If anything went wrong, point out what command to rerun manually to
416 420 # see the actual errors and individual summary
417 421 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
418 422 for name, failed_runner in failed:
419 423 print '-'*40
420 424 print 'Runner failed:',name
421 425 print 'You may wish to rerun this one individually, with:'
422 426 print ' '.join(failed_runner.call_args)
423 427 print
424 428
425 429
426 430 def main():
427 431 for arg in sys.argv[1:]:
428 432 if arg.startswith('IPython'):
429 433 # This is in-process
430 434 run_iptest()
431 435 else:
432 436 # This starts subprocesses
433 437 run_iptestall()
434 438
435 439
436 440 if __name__ == '__main__':
437 441 main()
General Comments 0
You need to be logged in to leave comments. Login now