##// END OF EJS Templates
Allow IPython to run without sqlite3...
MinRK -
Show More
@@ -1,909 +1,948 b''
1 1 """ History related magics and functionality """
2 2 #-----------------------------------------------------------------------------
3 3 # Copyright (C) 2010 The IPython Development Team.
4 4 #
5 5 # Distributed under the terms of the BSD License.
6 6 #
7 7 # The full license is in the file COPYING.txt, distributed with this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13 from __future__ import print_function
14 14
15 15 # Stdlib imports
16 16 import atexit
17 17 import datetime
18 18 import os
19 19 import re
20 import sqlite3
20 try:
21 import sqlite3
22 except ImportError:
23 sqlite3 = None
21 24 import threading
22 25
23 26 # Our own packages
24 27 from IPython.config.configurable import Configurable
25
28 from IPython.external.decorator import decorator
26 29 from IPython.testing.skipdoctest import skip_doctest
27 30 from IPython.utils import io
28 31 from IPython.utils.path import locate_profile
29 32 from IPython.utils.traitlets import Bool, Dict, Instance, Int, CInt, List, Unicode
30 33 from IPython.utils.warn import warn
31 34
32 35 #-----------------------------------------------------------------------------
33 36 # Classes and functions
34 37 #-----------------------------------------------------------------------------
35 38
39 class DummyDB(object):
40 """Dummy DB that will act as a black hole for history.
41
42 Only used in the absence of sqlite"""
43 def execute(*args, **kwargs):
44 return []
45
46 def commit(self, *args, **kwargs):
47 pass
48
49 def __enter__(self, *args, **kwargs):
50 pass
51
52 def __exit__(self, *args, **kwargs):
53 pass
54
55 @decorator
56 def needs_sqlite(f,*a,**kw):
57 """return an empty list in the absence of sqlite"""
58 if sqlite3 is None:
59 return []
60 else:
61 return f(*a,**kw)
62
36 63 class HistoryAccessor(Configurable):
37 64 """Access the history database without adding to it.
38 65
39 66 This is intended for use by standalone history tools. IPython shells use
40 67 HistoryManager, below, which is a subclass of this."""
41 68 # String holding the path to the history file
42 69 hist_file = Unicode(config=True)
43 70
44 71 # The SQLite database
45 db = Instance(sqlite3.Connection)
72 if sqlite3:
73 db = Instance(sqlite3.Connection)
74 else:
75 db = Instance(DummyDB)
46 76
47 77 def __init__(self, profile='default', hist_file=u'', shell=None, config=None, **traits):
48 78 """Create a new history accessor.
49 79
50 80 Parameters
51 81 ----------
52 82 profile : str
53 83 The name of the profile from which to open history.
54 84 hist_file : str
55 85 Path to an SQLite history database stored by IPython. If specified,
56 86 hist_file overrides profile.
57 87 shell :
58 88 InteractiveShell object, for use by HistoryManager subclass
59 89 config :
60 90 Config object. hist_file can also be set through this.
61 91 """
62 92 # We need a pointer back to the shell for various tasks.
63 93 super(HistoryAccessor, self).__init__(shell=shell, config=config,
64 94 hist_file=hist_file, **traits)
65 95
66 96 if self.hist_file == u'':
67 97 # No one has set the hist_file, yet.
68 98 self.hist_file = self._get_hist_file_name(profile)
69 99
100 if sqlite3 is None:
101 warn("IPython History requires SQLite, your history will not be saved\n")
102 self.db = DummyDB()
103 return
104
70 105 try:
71 106 self.init_db()
72 107 except sqlite3.DatabaseError:
73 108 if os.path.isfile(self.hist_file):
74 109 # Try to move the file out of the way.
75 110 newpath = os.path.join(self.shell.profile_dir.location, "hist-corrupt.sqlite")
76 111 os.rename(self.hist_file, newpath)
77 112 print("ERROR! History file wasn't a valid SQLite database.",
78 113 "It was moved to %s" % newpath, "and a new file created.")
79 114 self.init_db()
80 115 else:
81 116 # The hist_file is probably :memory: or something else.
82 117 raise
83 118
84 119 def _get_hist_file_name(self, profile='default'):
85 120 """Find the history file for the given profile name.
86 121
87 122 This is overridden by the HistoryManager subclass, to use the shell's
88 123 active profile.
89 124
90 125 Parameters
91 126 ----------
92 127 profile : str
93 128 The name of a profile which has a history file.
94 129 """
95 130 return os.path.join(locate_profile(profile), 'history.sqlite')
96 131
97 132 def init_db(self):
98 133 """Connect to the database, and create tables if necessary."""
99 134 # use detect_types so that timestamps return datetime objects
100 135 self.db = sqlite3.connect(self.hist_file, detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
101 136 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
102 137 primary key autoincrement, start timestamp,
103 138 end timestamp, num_cmds integer, remark text)""")
104 139 self.db.execute("""CREATE TABLE IF NOT EXISTS history
105 140 (session integer, line integer, source text, source_raw text,
106 141 PRIMARY KEY (session, line))""")
107 142 # Output history is optional, but ensure the table's there so it can be
108 143 # enabled later.
109 144 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
110 145 (session integer, line integer, output text,
111 146 PRIMARY KEY (session, line))""")
112 147 self.db.commit()
113 148
114 149 def writeout_cache(self):
115 150 """Overridden by HistoryManager to dump the cache before certain
116 151 database lookups."""
117 152 pass
118 153
119 154 ## -------------------------------
120 155 ## Methods for retrieving history:
121 156 ## -------------------------------
122 157 def _run_sql(self, sql, params, raw=True, output=False):
123 158 """Prepares and runs an SQL query for the history database.
124 159
125 160 Parameters
126 161 ----------
127 162 sql : str
128 163 Any filtering expressions to go after SELECT ... FROM ...
129 164 params : tuple
130 165 Parameters passed to the SQL query (to replace "?")
131 166 raw, output : bool
132 167 See :meth:`get_range`
133 168
134 169 Returns
135 170 -------
136 171 Tuples as :meth:`get_range`
137 172 """
138 173 toget = 'source_raw' if raw else 'source'
139 174 sqlfrom = "history"
140 175 if output:
141 176 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
142 177 toget = "history.%s, output_history.output" % toget
143 178 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
144 179 (toget, sqlfrom) + sql, params)
145 180 if output: # Regroup into 3-tuples, and parse JSON
146 181 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
147 182 return cur
148 183
184 @needs_sqlite
149 185 def get_session_info(self, session=0):
150 186 """get info about a session
151 187
152 188 Parameters
153 189 ----------
154 190
155 191 session : int
156 192 Session number to retrieve. The current session is 0, and negative
157 193 numbers count back from current session, so -1 is previous session.
158 194
159 195 Returns
160 196 -------
161 197
162 198 (session_id [int], start [datetime], end [datetime], num_cmds [int], remark [unicode])
163 199
164 200 Sessions that are running or did not exit cleanly will have `end=None`
165 201 and `num_cmds=None`.
166 202
167 203 """
168 204
169 205 if session <= 0:
170 206 session += self.session_number
171 207
172 208 query = "SELECT * from sessions where session == ?"
173 209 return self.db.execute(query, (session,)).fetchone()
174 210
175 211 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
176 212 """Get the last n lines from the history database.
177 213
178 214 Parameters
179 215 ----------
180 216 n : int
181 217 The number of lines to get
182 218 raw, output : bool
183 219 See :meth:`get_range`
184 220 include_latest : bool
185 221 If False (default), n+1 lines are fetched, and the latest one
186 222 is discarded. This is intended to be used where the function
187 223 is called by a user command, which it should not return.
188 224
189 225 Returns
190 226 -------
191 227 Tuples as :meth:`get_range`
192 228 """
193 229 self.writeout_cache()
194 230 if not include_latest:
195 231 n += 1
196 232 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
197 233 (n,), raw=raw, output=output)
198 234 if not include_latest:
199 235 return reversed(list(cur)[1:])
200 236 return reversed(list(cur))
201 237
202 238 def search(self, pattern="*", raw=True, search_raw=True,
203 239 output=False):
204 240 """Search the database using unix glob-style matching (wildcards
205 241 * and ?).
206 242
207 243 Parameters
208 244 ----------
209 245 pattern : str
210 246 The wildcarded pattern to match when searching
211 247 search_raw : bool
212 248 If True, search the raw input, otherwise, the parsed input
213 249 raw, output : bool
214 250 See :meth:`get_range`
215 251
216 252 Returns
217 253 -------
218 254 Tuples as :meth:`get_range`
219 255 """
220 256 tosearch = "source_raw" if search_raw else "source"
221 257 if output:
222 258 tosearch = "history." + tosearch
223 259 self.writeout_cache()
224 260 return self._run_sql("WHERE %s GLOB ?" % tosearch, (pattern,),
225 261 raw=raw, output=output)
226 262
227 263 def get_range(self, session, start=1, stop=None, raw=True,output=False):
228 264 """Retrieve input by session.
229 265
230 266 Parameters
231 267 ----------
232 268 session : int
233 269 Session number to retrieve.
234 270 start : int
235 271 First line to retrieve.
236 272 stop : int
237 273 End of line range (excluded from output itself). If None, retrieve
238 274 to the end of the session.
239 275 raw : bool
240 276 If True, return untranslated input
241 277 output : bool
242 278 If True, attempt to include output. This will be 'real' Python
243 279 objects for the current session, or text reprs from previous
244 280 sessions if db_log_output was enabled at the time. Where no output
245 281 is found, None is used.
246 282
247 283 Returns
248 284 -------
249 285 An iterator over the desired lines. Each line is a 3-tuple, either
250 286 (session, line, input) if output is False, or
251 287 (session, line, (input, output)) if output is True.
252 288 """
253 289 if stop:
254 290 lineclause = "line >= ? AND line < ?"
255 291 params = (session, start, stop)
256 292 else:
257 293 lineclause = "line>=?"
258 294 params = (session, start)
259 295
260 296 return self._run_sql("WHERE session==? AND %s""" % lineclause,
261 297 params, raw=raw, output=output)
262 298
263 299 def get_range_by_str(self, rangestr, raw=True, output=False):
264 300 """Get lines of history from a string of ranges, as used by magic
265 301 commands %hist, %save, %macro, etc.
266 302
267 303 Parameters
268 304 ----------
269 305 rangestr : str
270 306 A string specifying ranges, e.g. "5 ~2/1-4". See
271 307 :func:`magic_history` for full details.
272 308 raw, output : bool
273 309 As :meth:`get_range`
274 310
275 311 Returns
276 312 -------
277 313 Tuples as :meth:`get_range`
278 314 """
279 315 for sess, s, e in extract_hist_ranges(rangestr):
280 316 for line in self.get_range(sess, s, e, raw=raw, output=output):
281 317 yield line
282 318
283 319
284 320 class HistoryManager(HistoryAccessor):
285 321 """A class to organize all history-related functionality in one place.
286 322 """
287 323 # Public interface
288 324
289 325 # An instance of the IPython shell we are attached to
290 326 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
291 327 # Lists to hold processed and raw history. These start with a blank entry
292 328 # so that we can index them starting from 1
293 329 input_hist_parsed = List([""])
294 330 input_hist_raw = List([""])
295 331 # A list of directories visited during session
296 332 dir_hist = List()
297 333 def _dir_hist_default(self):
298 334 try:
299 335 return [os.getcwdu()]
300 336 except OSError:
301 337 return []
302 338
303 339 # A dict of output history, keyed with ints from the shell's
304 340 # execution count.
305 341 output_hist = Dict()
306 342 # The text/plain repr of outputs.
307 343 output_hist_reprs = Dict()
308 344
309 345 # The number of the current session in the history database
310 346 session_number = CInt()
311 347 # Should we log output to the database? (default no)
312 348 db_log_output = Bool(False, config=True)
313 349 # Write to database every x commands (higher values save disk access & power)
314 350 # Values of 1 or less effectively disable caching.
315 351 db_cache_size = Int(0, config=True)
316 352 # The input and output caches
317 353 db_input_cache = List()
318 354 db_output_cache = List()
319 355
320 356 # History saving in separate thread
321 357 save_thread = Instance('IPython.core.history.HistorySavingThread')
322 358 try: # Event is a function returning an instance of _Event...
323 359 save_flag = Instance(threading._Event)
324 360 except AttributeError: # ...until Python 3.3, when it's a class.
325 361 save_flag = Instance(threading.Event)
326 362
327 363 # Private interface
328 364 # Variables used to store the three last inputs from the user. On each new
329 365 # history update, we populate the user's namespace with these, shifted as
330 366 # necessary.
331 367 _i00 = Unicode(u'')
332 368 _i = Unicode(u'')
333 369 _ii = Unicode(u'')
334 370 _iii = Unicode(u'')
335 371
336 372 # A regex matching all forms of the exit command, so that we don't store
337 373 # them in the history (it's annoying to rewind the first entry and land on
338 374 # an exit call).
339 375 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
340 376
341 377 def __init__(self, shell=None, config=None, **traits):
342 378 """Create a new history manager associated with a shell instance.
343 379 """
344 380 # We need a pointer back to the shell for various tasks.
345 381 super(HistoryManager, self).__init__(shell=shell, config=config,
346 382 **traits)
347 383 self.save_flag = threading.Event()
348 384 self.db_input_cache_lock = threading.Lock()
349 385 self.db_output_cache_lock = threading.Lock()
350 386 self.save_thread = HistorySavingThread(self)
351 387 self.save_thread.start()
352 388
353 389 self.new_session()
354
390
355 391 def _get_hist_file_name(self, profile=None):
356 392 """Get default history file name based on the Shell's profile.
357 393
358 394 The profile parameter is ignored, but must exist for compatibility with
359 395 the parent class."""
360 396 profile_dir = self.shell.profile_dir.location
361 397 return os.path.join(profile_dir, 'history.sqlite')
362 398
399 @needs_sqlite
363 400 def new_session(self, conn=None):
364 401 """Get a new session number."""
365 402 if conn is None:
366 403 conn = self.db
367 404
368 405 with conn:
369 406 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
370 407 NULL, "") """, (datetime.datetime.now(),))
371 408 self.session_number = cur.lastrowid
372 409
373 410 def end_session(self):
374 411 """Close the database session, filling in the end time and line count."""
375 412 self.writeout_cache()
376 413 with self.db:
377 414 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
378 415 session==?""", (datetime.datetime.now(),
379 416 len(self.input_hist_parsed)-1, self.session_number))
380 417 self.session_number = 0
381 418
382 419 def name_session(self, name):
383 420 """Give the current session a name in the history database."""
384 421 with self.db:
385 422 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
386 423 (name, self.session_number))
387 424
388 425 def reset(self, new_session=True):
389 426 """Clear the session history, releasing all object references, and
390 427 optionally open a new session."""
391 428 self.output_hist.clear()
392 429 # The directory history can't be completely empty
393 430 self.dir_hist[:] = [os.getcwdu()]
394 431
395 432 if new_session:
396 433 if self.session_number:
397 434 self.end_session()
398 435 self.input_hist_parsed[:] = [""]
399 436 self.input_hist_raw[:] = [""]
400 437 self.new_session()
401 438
402 439 # ------------------------------
403 440 # Methods for retrieving history
404 441 # ------------------------------
405 442 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
406 443 """Get input and output history from the current session. Called by
407 444 get_range, and takes similar parameters."""
408 445 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
409 446
410 447 n = len(input_hist)
411 448 if start < 0:
412 449 start += n
413 450 if not stop or (stop > n):
414 451 stop = n
415 452 elif stop < 0:
416 453 stop += n
417 454
418 455 for i in range(start, stop):
419 456 if output:
420 457 line = (input_hist[i], self.output_hist_reprs.get(i))
421 458 else:
422 459 line = input_hist[i]
423 460 yield (0, i, line)
424 461
425 462 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
426 463 """Retrieve input by session.
427 464
428 465 Parameters
429 466 ----------
430 467 session : int
431 468 Session number to retrieve. The current session is 0, and negative
432 469 numbers count back from current session, so -1 is previous session.
433 470 start : int
434 471 First line to retrieve.
435 472 stop : int
436 473 End of line range (excluded from output itself). If None, retrieve
437 474 to the end of the session.
438 475 raw : bool
439 476 If True, return untranslated input
440 477 output : bool
441 478 If True, attempt to include output. This will be 'real' Python
442 479 objects for the current session, or text reprs from previous
443 480 sessions if db_log_output was enabled at the time. Where no output
444 481 is found, None is used.
445 482
446 483 Returns
447 484 -------
448 485 An iterator over the desired lines. Each line is a 3-tuple, either
449 486 (session, line, input) if output is False, or
450 487 (session, line, (input, output)) if output is True.
451 488 """
452 489 if session <= 0:
453 490 session += self.session_number
454 491 if session==self.session_number: # Current session
455 492 return self._get_range_session(start, stop, raw, output)
456 493 return super(HistoryManager, self).get_range(session, start, stop, raw, output)
457 494
458 495 ## ----------------------------
459 496 ## Methods for storing history:
460 497 ## ----------------------------
461 498 def store_inputs(self, line_num, source, source_raw=None):
462 499 """Store source and raw input in history and create input cache
463 500 variables _i*.
464 501
465 502 Parameters
466 503 ----------
467 504 line_num : int
468 505 The prompt number of this input.
469 506
470 507 source : str
471 508 Python input.
472 509
473 510 source_raw : str, optional
474 511 If given, this is the raw input without any IPython transformations
475 512 applied to it. If not given, ``source`` is used.
476 513 """
477 514 if source_raw is None:
478 515 source_raw = source
479 516 source = source.rstrip('\n')
480 517 source_raw = source_raw.rstrip('\n')
481 518
482 519 # do not store exit/quit commands
483 520 if self._exit_re.match(source_raw.strip()):
484 521 return
485 522
486 523 self.input_hist_parsed.append(source)
487 524 self.input_hist_raw.append(source_raw)
488 525
489 526 with self.db_input_cache_lock:
490 527 self.db_input_cache.append((line_num, source, source_raw))
491 528 # Trigger to flush cache and write to DB.
492 529 if len(self.db_input_cache) >= self.db_cache_size:
493 530 self.save_flag.set()
494 531
495 532 # update the auto _i variables
496 533 self._iii = self._ii
497 534 self._ii = self._i
498 535 self._i = self._i00
499 536 self._i00 = source_raw
500 537
501 538 # hackish access to user namespace to create _i1,_i2... dynamically
502 539 new_i = '_i%s' % line_num
503 540 to_main = {'_i': self._i,
504 541 '_ii': self._ii,
505 542 '_iii': self._iii,
506 543 new_i : self._i00 }
507 544 self.shell.user_ns.update(to_main)
508 545
509 546 def store_output(self, line_num):
510 547 """If database output logging is enabled, this saves all the
511 548 outputs from the indicated prompt number to the database. It's
512 549 called by run_cell after code has been executed.
513 550
514 551 Parameters
515 552 ----------
516 553 line_num : int
517 554 The line number from which to save outputs
518 555 """
519 556 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
520 557 return
521 558 output = self.output_hist_reprs[line_num]
522 559
523 560 with self.db_output_cache_lock:
524 561 self.db_output_cache.append((line_num, output))
525 562 if self.db_cache_size <= 1:
526 563 self.save_flag.set()
527 564
528 565 def _writeout_input_cache(self, conn):
529 566 with conn:
530 567 for line in self.db_input_cache:
531 568 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
532 569 (self.session_number,)+line)
533 570
534 571 def _writeout_output_cache(self, conn):
535 572 with conn:
536 573 for line in self.db_output_cache:
537 574 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
538 575 (self.session_number,)+line)
539 576
577 @needs_sqlite
540 578 def writeout_cache(self, conn=None):
541 579 """Write any entries in the cache to the database."""
542 580 if conn is None:
543 581 conn = self.db
544 582
545 583 with self.db_input_cache_lock:
546 584 try:
547 585 self._writeout_input_cache(conn)
548 586 except sqlite3.IntegrityError:
549 587 self.new_session(conn)
550 588 print("ERROR! Session/line number was not unique in",
551 589 "database. History logging moved to new session",
552 590 self.session_number)
553 591 try: # Try writing to the new session. If this fails, don't recurse
554 592 self._writeout_input_cache(conn)
555 593 except sqlite3.IntegrityError:
556 594 pass
557 595 finally:
558 596 self.db_input_cache = []
559 597
560 598 with self.db_output_cache_lock:
561 599 try:
562 600 self._writeout_output_cache(conn)
563 601 except sqlite3.IntegrityError:
564 602 print("!! Session/line number for output was not unique",
565 603 "in database. Output will not be stored.")
566 604 finally:
567 605 self.db_output_cache = []
568 606
569 607
570 608 class HistorySavingThread(threading.Thread):
571 609 """This thread takes care of writing history to the database, so that
572 610 the UI isn't held up while that happens.
573 611
574 612 It waits for the HistoryManager's save_flag to be set, then writes out
575 613 the history cache. The main thread is responsible for setting the flag when
576 614 the cache size reaches a defined threshold."""
577 615 daemon = True
578 616 stop_now = False
579 617 def __init__(self, history_manager):
580 618 super(HistorySavingThread, self).__init__()
581 619 self.history_manager = history_manager
582 620 atexit.register(self.stop)
583 621
622 @needs_sqlite
584 623 def run(self):
585 624 # We need a separate db connection per thread:
586 625 try:
587 626 self.db = sqlite3.connect(self.history_manager.hist_file)
588 627 while True:
589 628 self.history_manager.save_flag.wait()
590 629 if self.stop_now:
591 630 return
592 631 self.history_manager.save_flag.clear()
593 632 self.history_manager.writeout_cache(self.db)
594 633 except Exception as e:
595 634 print(("The history saving thread hit an unexpected error (%s)."
596 635 "History will not be written to the database.") % repr(e))
597 636
598 637 def stop(self):
599 638 """This can be called from the main thread to safely stop this thread.
600 639
601 640 Note that it does not attempt to write out remaining history before
602 641 exiting. That should be done by calling the HistoryManager's
603 642 end_session method."""
604 643 self.stop_now = True
605 644 self.history_manager.save_flag.set()
606 645 self.join()
607 646
608 647
609 648 # To match, e.g. ~5/8-~2/3
610 649 range_re = re.compile(r"""
611 650 ((?P<startsess>~?\d+)/)?
612 651 (?P<start>\d+) # Only the start line num is compulsory
613 652 ((?P<sep>[\-:])
614 653 ((?P<endsess>~?\d+)/)?
615 654 (?P<end>\d+))?
616 655 $""", re.VERBOSE)
617 656
618 657 def extract_hist_ranges(ranges_str):
619 658 """Turn a string of history ranges into 3-tuples of (session, start, stop).
620 659
621 660 Examples
622 661 --------
623 662 list(extract_input_ranges("~8/5-~7/4 2"))
624 663 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
625 664 """
626 665 for range_str in ranges_str.split():
627 666 rmatch = range_re.match(range_str)
628 667 if not rmatch:
629 668 continue
630 669 start = int(rmatch.group("start"))
631 670 end = rmatch.group("end")
632 671 end = int(end) if end else start+1 # If no end specified, get (a, a+1)
633 672 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
634 673 end += 1
635 674 startsess = rmatch.group("startsess") or "0"
636 675 endsess = rmatch.group("endsess") or startsess
637 676 startsess = int(startsess.replace("~","-"))
638 677 endsess = int(endsess.replace("~","-"))
639 678 assert endsess >= startsess
640 679
641 680 if endsess == startsess:
642 681 yield (startsess, start, end)
643 682 continue
644 683 # Multiple sessions in one range:
645 684 yield (startsess, start, None)
646 685 for sess in range(startsess+1, endsess):
647 686 yield (sess, 1, None)
648 687 yield (endsess, 1, end)
649 688
650 689 def _format_lineno(session, line):
651 690 """Helper function to format line numbers properly."""
652 691 if session == 0:
653 692 return str(line)
654 693 return "%s#%s" % (session, line)
655 694
656 695 @skip_doctest
657 696 def magic_history(self, parameter_s = ''):
658 697 """Print input history (_i<n> variables), with most recent last.
659 698
660 699 %history -> print at most 40 inputs (some may be multi-line)\\
661 700 %history n -> print at most n inputs\\
662 701 %history n1 n2 -> print inputs between n1 and n2 (n2 not included)\\
663 702
664 703 By default, input history is printed without line numbers so it can be
665 704 directly pasted into an editor. Use -n to show them.
666 705
667 706 Ranges of history can be indicated using the syntax:
668 707 4 : Line 4, current session
669 708 4-6 : Lines 4-6, current session
670 709 243/1-5: Lines 1-5, session 243
671 710 ~2/7 : Line 7, session 2 before current
672 711 ~8/1-~6/5 : From the first line of 8 sessions ago, to the fifth line
673 712 of 6 sessions ago.
674 713 Multiple ranges can be entered, separated by spaces
675 714
676 715 The same syntax is used by %macro, %save, %edit, %rerun
677 716
678 717 Options:
679 718
680 719 -n: print line numbers for each input.
681 720 This feature is only available if numbered prompts are in use.
682 721
683 722 -o: also print outputs for each input.
684 723
685 724 -p: print classic '>>>' python prompts before each input. This is useful
686 725 for making documentation, and in conjunction with -o, for producing
687 726 doctest-ready output.
688 727
689 728 -r: (default) print the 'raw' history, i.e. the actual commands you typed.
690 729
691 730 -t: print the 'translated' history, as IPython understands it. IPython
692 731 filters your input and converts it all into valid Python source before
693 732 executing it (things like magics or aliases are turned into function
694 733 calls, for example). With this option, you'll see the native history
695 734 instead of the user-entered version: '%cd /' will be seen as
696 735 'get_ipython().magic("%cd /")' instead of '%cd /'.
697 736
698 737 -g: treat the arg as a pattern to grep for in (full) history.
699 738 This includes the saved history (almost all commands ever written).
700 739 Use '%hist -g' to show full saved history (may be very long).
701 740
702 741 -l: get the last n lines from all sessions. Specify n as a single arg, or
703 742 the default is the last 10 lines.
704 743
705 744 -f FILENAME: instead of printing the output to the screen, redirect it to
706 745 the given file. The file is always overwritten, though IPython asks for
707 746 confirmation first if it already exists.
708 747
709 748 Examples
710 749 --------
711 750 ::
712 751
713 752 In [6]: %hist -n 4 6
714 753 4:a = 12
715 754 5:print a**2
716 755
717 756 """
718 757
719 758 if not self.shell.displayhook.do_full_cache:
720 759 print('This feature is only available if numbered prompts are in use.')
721 760 return
722 761 opts,args = self.parse_options(parameter_s,'noprtglf:',mode='string')
723 762
724 763 # For brevity
725 764 history_manager = self.shell.history_manager
726 765
727 766 def _format_lineno(session, line):
728 767 """Helper function to format line numbers properly."""
729 768 if session in (0, history_manager.session_number):
730 769 return str(line)
731 770 return "%s/%s" % (session, line)
732 771
733 772 # Check if output to specific file was requested.
734 773 try:
735 774 outfname = opts['f']
736 775 except KeyError:
737 776 outfile = io.stdout # default
738 777 # We don't want to close stdout at the end!
739 778 close_at_end = False
740 779 else:
741 780 if os.path.exists(outfname):
742 781 if not io.ask_yes_no("File %r exists. Overwrite?" % outfname):
743 782 print('Aborting.')
744 783 return
745 784
746 785 outfile = open(outfname,'w')
747 786 close_at_end = True
748 787
749 788 print_nums = 'n' in opts
750 789 get_output = 'o' in opts
751 790 pyprompts = 'p' in opts
752 791 # Raw history is the default
753 792 raw = not('t' in opts)
754 793
755 794 default_length = 40
756 795 pattern = None
757 796
758 797 if 'g' in opts: # Glob search
759 798 pattern = "*" + args + "*" if args else "*"
760 799 hist = history_manager.search(pattern, raw=raw, output=get_output)
761 800 print_nums = True
762 801 elif 'l' in opts: # Get 'tail'
763 802 try:
764 803 n = int(args)
765 804 except ValueError, IndexError:
766 805 n = 10
767 806 hist = history_manager.get_tail(n, raw=raw, output=get_output)
768 807 else:
769 808 if args: # Get history by ranges
770 809 hist = history_manager.get_range_by_str(args, raw, get_output)
771 810 else: # Just get history for the current session
772 811 hist = history_manager.get_range(raw=raw, output=get_output)
773 812
774 813 # We could be displaying the entire history, so let's not try to pull it
775 814 # into a list in memory. Anything that needs more space will just misalign.
776 815 width = 4
777 816
778 817 for session, lineno, inline in hist:
779 818 # Print user history with tabs expanded to 4 spaces. The GUI clients
780 819 # use hard tabs for easier usability in auto-indented code, but we want
781 820 # to produce PEP-8 compliant history for safe pasting into an editor.
782 821 if get_output:
783 822 inline, output = inline
784 823 inline = inline.expandtabs(4).rstrip()
785 824
786 825 multiline = "\n" in inline
787 826 line_sep = '\n' if multiline else ' '
788 827 if print_nums:
789 828 print('%s:%s' % (_format_lineno(session, lineno).rjust(width),
790 829 line_sep), file=outfile, end='')
791 830 if pyprompts:
792 831 print(">>> ", end="", file=outfile)
793 832 if multiline:
794 833 inline = "\n... ".join(inline.splitlines()) + "\n..."
795 834 print(inline, file=outfile)
796 835 if get_output and output:
797 836 print(output, file=outfile)
798 837
799 838 if close_at_end:
800 839 outfile.close()
801 840
802 841
803 842 def magic_rep(self, arg):
804 843 r"""Repeat a command, or get command to input line for editing. %recall and
805 844 %rep are equivalent.
806 845
807 846 - %recall (no arguments):
808 847
809 848 Place a string version of last computation result (stored in the special '_'
810 849 variable) to the next input prompt. Allows you to create elaborate command
811 850 lines without using copy-paste::
812 851
813 852 In[1]: l = ["hei", "vaan"]
814 853 In[2]: "".join(l)
815 854 Out[2]: heivaan
816 855 In[3]: %rep
817 856 In[4]: heivaan_ <== cursor blinking
818 857
819 858 %recall 45
820 859
821 860 Place history line 45 on the next input prompt. Use %hist to find
822 861 out the number.
823 862
824 863 %recall 1-4
825 864
826 865 Combine the specified lines into one cell, and place it on the next
827 866 input prompt. See %history for the slice syntax.
828 867
829 868 %recall foo+bar
830 869
831 870 If foo+bar can be evaluated in the user namespace, the result is
832 871 placed at the next input prompt. Otherwise, the history is searched
833 872 for lines which contain that substring, and the most recent one is
834 873 placed at the next input prompt.
835 874 """
836 875 if not arg: # Last output
837 876 self.set_next_input(str(self.shell.user_ns["_"]))
838 877 return
839 878 # Get history range
840 879 histlines = self.history_manager.get_range_by_str(arg)
841 880 cmd = "\n".join(x[2] for x in histlines)
842 881 if cmd:
843 882 self.set_next_input(cmd.rstrip())
844 883 return
845 884
846 885 try: # Variable in user namespace
847 886 cmd = str(eval(arg, self.shell.user_ns))
848 887 except Exception: # Search for term in history
849 888 histlines = self.history_manager.search("*"+arg+"*")
850 889 for h in reversed([x[2] for x in histlines]):
851 890 if 'rep' in h:
852 891 continue
853 892 self.set_next_input(h.rstrip())
854 893 return
855 894 else:
856 895 self.set_next_input(cmd.rstrip())
857 896 print("Couldn't evaluate or find in history:", arg)
858 897
859 898 def magic_rerun(self, parameter_s=''):
860 899 """Re-run previous input
861 900
862 901 By default, you can specify ranges of input history to be repeated
863 902 (as with %history). With no arguments, it will repeat the last line.
864 903
865 904 Options:
866 905
867 906 -l <n> : Repeat the last n lines of input, not including the
868 907 current command.
869 908
870 909 -g foo : Repeat the most recent line which contains foo
871 910 """
872 911 opts, args = self.parse_options(parameter_s, 'l:g:', mode='string')
873 912 if "l" in opts: # Last n lines
874 913 n = int(opts['l'])
875 914 hist = self.history_manager.get_tail(n)
876 915 elif "g" in opts: # Search
877 916 p = "*"+opts['g']+"*"
878 917 hist = list(self.history_manager.search(p))
879 918 for l in reversed(hist):
880 919 if "rerun" not in l[2]:
881 920 hist = [l] # The last match which isn't a %rerun
882 921 break
883 922 else:
884 923 hist = [] # No matches except %rerun
885 924 elif args: # Specify history ranges
886 925 hist = self.history_manager.get_range_by_str(args)
887 926 else: # Last line
888 927 hist = self.history_manager.get_tail(1)
889 928 hist = [x[2] for x in hist]
890 929 if not hist:
891 930 print("No lines in history match specification")
892 931 return
893 932 histlines = "\n".join(hist)
894 933 print("=== Executing: ===")
895 934 print(histlines)
896 935 print("=== Output: ===")
897 936 self.run_cell("\n".join(hist), store_history=False)
898 937
899 938
900 939 def init_ipython(ip):
901 940 ip.define_magic("rep", magic_rep)
902 941 ip.define_magic("recall", magic_rep)
903 942 ip.define_magic("rerun", magic_rerun)
904 943 ip.define_magic("hist",magic_history) # Alternative name
905 944 ip.define_magic("history",magic_history)
906 945
907 946 # XXX - ipy_completers are in quarantine, need to be updated to new apis
908 947 #import ipy_completers
909 948 #ipy_completers.quick_completer('%hist' ,'-g -t -r -n')
@@ -1,452 +1,460 b''
1 1 """Tests for various magic functions.
2 2
3 3 Needs to be run by nose (to make ipython session available).
4 4 """
5 5 from __future__ import absolute_import
6 6
7 7 #-----------------------------------------------------------------------------
8 8 # Imports
9 9 #-----------------------------------------------------------------------------
10 10
11 11 import os
12 12 import sys
13 13 import tempfile
14 14 import types
15 15 from StringIO import StringIO
16 16
17 17 import nose.tools as nt
18 18
19 19 from IPython.utils.path import get_long_path_name
20 20 from IPython.testing import decorators as dec
21 21 from IPython.testing import tools as tt
22 22 from IPython.utils import py3compat
23 23
24 24 #-----------------------------------------------------------------------------
25 25 # Test functions begin
26 26 #-----------------------------------------------------------------------------
27 27 def test_rehashx():
28 28 # clear up everything
29 29 _ip = get_ipython()
30 30 _ip.alias_manager.alias_table.clear()
31 31 del _ip.db['syscmdlist']
32 32
33 33 _ip.magic('rehashx')
34 34 # Practically ALL ipython development systems will have more than 10 aliases
35 35
36 36 yield (nt.assert_true, len(_ip.alias_manager.alias_table) > 10)
37 37 for key, val in _ip.alias_manager.alias_table.iteritems():
38 38 # we must strip dots from alias names
39 39 nt.assert_true('.' not in key)
40 40
41 41 # rehashx must fill up syscmdlist
42 42 scoms = _ip.db['syscmdlist']
43 43 yield (nt.assert_true, len(scoms) > 10)
44 44
45 45
46 46 def test_magic_parse_options():
47 47 """Test that we don't mangle paths when parsing magic options."""
48 48 ip = get_ipython()
49 49 path = 'c:\\x'
50 50 opts = ip.parse_options('-f %s' % path,'f:')[0]
51 51 # argv splitting is os-dependent
52 52 if os.name == 'posix':
53 53 expected = 'c:x'
54 54 else:
55 55 expected = path
56 56 nt.assert_equals(opts['f'], expected)
57 57
58
58
59 @dec.skip_without('sqlite3')
59 60 def doctest_hist_f():
60 61 """Test %hist -f with temporary filename.
61 62
62 63 In [9]: import tempfile
63 64
64 65 In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-')
65 66
66 67 In [11]: %hist -nl -f $tfile 3
67 68
68 69 In [13]: import os; os.unlink(tfile)
69 70 """
70 71
71 72
73 @dec.skip_without('sqlite3')
72 74 def doctest_hist_r():
73 75 """Test %hist -r
74 76
75 77 XXX - This test is not recording the output correctly. For some reason, in
76 78 testing mode the raw history isn't getting populated. No idea why.
77 79 Disabling the output checking for now, though at least we do run it.
78 80
79 81 In [1]: 'hist' in _ip.lsmagic()
80 82 Out[1]: True
81 83
82 84 In [2]: x=1
83 85
84 86 In [3]: %hist -rl 2
85 87 x=1 # random
86 88 %hist -r 2
87 89 """
88 90
91
92 @dec.skip_without('sqlite3')
89 93 def doctest_hist_op():
90 94 """Test %hist -op
91 95
92 96 In [1]: class b(float):
93 97 ...: pass
94 98 ...:
95 99
96 100 In [2]: class s(object):
97 101 ...: def __str__(self):
98 102 ...: return 's'
99 103 ...:
100 104
101 105 In [3]:
102 106
103 107 In [4]: class r(b):
104 108 ...: def __repr__(self):
105 109 ...: return 'r'
106 110 ...:
107 111
108 112 In [5]: class sr(s,r): pass
109 113 ...:
110 114
111 115 In [6]:
112 116
113 117 In [7]: bb=b()
114 118
115 119 In [8]: ss=s()
116 120
117 121 In [9]: rr=r()
118 122
119 123 In [10]: ssrr=sr()
120 124
121 125 In [11]: 4.5
122 126 Out[11]: 4.5
123 127
124 128 In [12]: str(ss)
125 129 Out[12]: 's'
126 130
127 131 In [13]:
128 132
129 133 In [14]: %hist -op
130 134 >>> class b:
131 135 ... pass
132 136 ...
133 137 >>> class s(b):
134 138 ... def __str__(self):
135 139 ... return 's'
136 140 ...
137 141 >>>
138 142 >>> class r(b):
139 143 ... def __repr__(self):
140 144 ... return 'r'
141 145 ...
142 146 >>> class sr(s,r): pass
143 147 >>>
144 148 >>> bb=b()
145 149 >>> ss=s()
146 150 >>> rr=r()
147 151 >>> ssrr=sr()
148 152 >>> 4.5
149 153 4.5
150 154 >>> str(ss)
151 155 's'
152 156 >>>
153 157 """
154
158
159
160 @dec.skip_without('sqlite3')
155 161 def test_macro():
156 162 ip = get_ipython()
157 163 ip.history_manager.reset() # Clear any existing history.
158 164 cmds = ["a=1", "def b():\n return a**2", "print(a,b())"]
159 165 for i, cmd in enumerate(cmds, start=1):
160 166 ip.history_manager.store_inputs(i, cmd)
161 167 ip.magic("macro test 1-3")
162 168 nt.assert_equal(ip.user_ns["test"].value, "\n".join(cmds)+"\n")
163 169
164 170 # List macros.
165 171 assert "test" in ip.magic("macro")
166 172
173
174 @dec.skip_without('sqlite3')
167 175 def test_macro_run():
168 176 """Test that we can run a multi-line macro successfully."""
169 177 ip = get_ipython()
170 178 ip.history_manager.reset()
171 179 cmds = ["a=10", "a+=1", py3compat.doctest_refactor_print("print a"),
172 180 "%macro test 2-3"]
173 181 for cmd in cmds:
174 182 ip.run_cell(cmd, store_history=True)
175 183 nt.assert_equal(ip.user_ns["test"].value,
176 184 py3compat.doctest_refactor_print("a+=1\nprint a\n"))
177 185 with tt.AssertPrints("12"):
178 186 ip.run_cell("test")
179 187 with tt.AssertPrints("13"):
180 188 ip.run_cell("test")
181 189
182 190
183 191 # XXX failing for now, until we get clearcmd out of quarantine. But we should
184 192 # fix this and revert the skip to happen only if numpy is not around.
185 193 #@dec.skipif_not_numpy
186 194 @dec.skip_known_failure
187 195 def test_numpy_clear_array_undec():
188 196 from IPython.extensions import clearcmd
189 197
190 198 _ip.ex('import numpy as np')
191 199 _ip.ex('a = np.empty(2)')
192 200 yield (nt.assert_true, 'a' in _ip.user_ns)
193 201 _ip.magic('clear array')
194 202 yield (nt.assert_false, 'a' in _ip.user_ns)
195 203
196 204
197 205 # Multiple tests for clipboard pasting
198 206 @dec.parametric
199 207 def test_paste():
200 208 _ip = get_ipython()
201 209 def paste(txt, flags='-q'):
202 210 """Paste input text, by default in quiet mode"""
203 211 hooks.clipboard_get = lambda : txt
204 212 _ip.magic('paste '+flags)
205 213
206 214 # Inject fake clipboard hook but save original so we can restore it later
207 215 hooks = _ip.hooks
208 216 user_ns = _ip.user_ns
209 217 original_clip = hooks.clipboard_get
210 218
211 219 try:
212 220 # Run tests with fake clipboard function
213 221 user_ns.pop('x', None)
214 222 paste('x=1')
215 223 yield nt.assert_equal(user_ns['x'], 1)
216 224
217 225 user_ns.pop('x', None)
218 226 paste('>>> x=2')
219 227 yield nt.assert_equal(user_ns['x'], 2)
220 228
221 229 paste("""
222 230 >>> x = [1,2,3]
223 231 >>> y = []
224 232 >>> for i in x:
225 233 ... y.append(i**2)
226 234 ...
227 235 """)
228 236 yield nt.assert_equal(user_ns['x'], [1,2,3])
229 237 yield nt.assert_equal(user_ns['y'], [1,4,9])
230 238
231 239 # Now, test that paste -r works
232 240 user_ns.pop('x', None)
233 241 yield nt.assert_false('x' in user_ns)
234 242 _ip.magic('paste -r')
235 243 yield nt.assert_equal(user_ns['x'], [1,2,3])
236 244
237 245 # Also test paste echoing, by temporarily faking the writer
238 246 w = StringIO()
239 247 writer = _ip.write
240 248 _ip.write = w.write
241 249 code = """
242 250 a = 100
243 251 b = 200"""
244 252 try:
245 253 paste(code,'')
246 254 out = w.getvalue()
247 255 finally:
248 256 _ip.write = writer
249 257 yield nt.assert_equal(user_ns['a'], 100)
250 258 yield nt.assert_equal(user_ns['b'], 200)
251 259 yield nt.assert_equal(out, code+"\n## -- End pasted text --\n")
252 260
253 261 finally:
254 262 # Restore original hook
255 263 hooks.clipboard_get = original_clip
256 264
257 265
258 266 def test_time():
259 267 _ip.magic('time None')
260 268
261 269
262 270 @py3compat.doctest_refactor_print
263 271 def doctest_time():
264 272 """
265 273 In [10]: %time None
266 274 CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
267 275 Wall time: 0.00 s
268 276
269 277 In [11]: def f(kmjy):
270 278 ....: %time print 2*kmjy
271 279
272 280 In [12]: f(3)
273 281 6
274 282 CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
275 283 Wall time: 0.00 s
276 284 """
277 285
278 286
279 287 def test_doctest_mode():
280 288 "Toggle doctest_mode twice, it should be a no-op and run without error"
281 289 _ip.magic('doctest_mode')
282 290 _ip.magic('doctest_mode')
283 291
284 292
285 293 def test_parse_options():
286 294 """Tests for basic options parsing in magics."""
287 295 # These are only the most minimal of tests, more should be added later. At
288 296 # the very least we check that basic text/unicode calls work OK.
289 297 nt.assert_equal(_ip.parse_options('foo', '')[1], 'foo')
290 298 nt.assert_equal(_ip.parse_options(u'foo', '')[1], u'foo')
291 299
292 300
293 301 def test_dirops():
294 302 """Test various directory handling operations."""
295 303 # curpath = lambda :os.path.splitdrive(os.getcwdu())[1].replace('\\','/')
296 304 curpath = os.getcwdu
297 305 startdir = os.getcwdu()
298 306 ipdir = _ip.ipython_dir
299 307 try:
300 308 _ip.magic('cd "%s"' % ipdir)
301 309 nt.assert_equal(curpath(), ipdir)
302 310 _ip.magic('cd -')
303 311 nt.assert_equal(curpath(), startdir)
304 312 _ip.magic('pushd "%s"' % ipdir)
305 313 nt.assert_equal(curpath(), ipdir)
306 314 _ip.magic('popd')
307 315 nt.assert_equal(curpath(), startdir)
308 316 finally:
309 317 os.chdir(startdir)
310 318
311 319
312 320 def check_cpaste(code, should_fail=False):
313 321 """Execute code via 'cpaste' and ensure it was executed, unless
314 322 should_fail is set.
315 323 """
316 324 _ip.user_ns['code_ran'] = False
317 325
318 326 src = StringIO()
319 327 src.encoding = None # IPython expects stdin to have an encoding attribute
320 328 src.write('\n')
321 329 src.write(code)
322 330 src.write('\n--\n')
323 331 src.seek(0)
324 332
325 333 stdin_save = sys.stdin
326 334 sys.stdin = src
327 335
328 336 try:
329 337 context = tt.AssertPrints if should_fail else tt.AssertNotPrints
330 338 with context("Traceback (most recent call last)"):
331 339 _ip.magic('cpaste')
332 340
333 341 if not should_fail:
334 342 assert _ip.user_ns['code_ran']
335 343 finally:
336 344 sys.stdin = stdin_save
337 345
338 346
339 347 def test_cpaste():
340 348 """Test cpaste magic"""
341 349
342 350 def run():
343 351 """Marker function: sets a flag when executed.
344 352 """
345 353 _ip.user_ns['code_ran'] = True
346 354 return 'run' # return string so '+ run()' doesn't result in success
347 355
348 356 tests = {'pass': ["> > > run()",
349 357 ">>> > run()",
350 358 "+++ run()",
351 359 "++ run()",
352 360 " >>> run()"],
353 361
354 362 'fail': ["+ + run()",
355 363 " ++ run()"]}
356 364
357 365 _ip.user_ns['run'] = run
358 366
359 367 for code in tests['pass']:
360 368 check_cpaste(code)
361 369
362 370 for code in tests['fail']:
363 371 check_cpaste(code, should_fail=True)
364 372
365 373 def test_xmode():
366 374 # Calling xmode three times should be a no-op
367 375 xmode = _ip.InteractiveTB.mode
368 376 for i in range(3):
369 377 _ip.magic("xmode")
370 378 nt.assert_equal(_ip.InteractiveTB.mode, xmode)
371 379
372 380 def test_reset_hard():
373 381 monitor = []
374 382 class A(object):
375 383 def __del__(self):
376 384 monitor.append(1)
377 385 def __repr__(self):
378 386 return "<A instance>"
379 387
380 388 _ip.user_ns["a"] = A()
381 389 _ip.run_cell("a")
382 390
383 391 nt.assert_equal(monitor, [])
384 392 _ip.magic_reset("-f")
385 393 nt.assert_equal(monitor, [1])
386 394
387 395 class TestXdel(tt.TempFileMixin):
388 396 def test_xdel(self):
389 397 """Test that references from %run are cleared by xdel."""
390 398 src = ("class A(object):\n"
391 399 " monitor = []\n"
392 400 " def __del__(self):\n"
393 401 " self.monitor.append(1)\n"
394 402 "a = A()\n")
395 403 self.mktmp(src)
396 404 # %run creates some hidden references...
397 405 _ip.magic("run %s" % self.fname)
398 406 # ... as does the displayhook.
399 407 _ip.run_cell("a")
400 408
401 409 monitor = _ip.user_ns["A"].monitor
402 410 nt.assert_equal(monitor, [])
403 411
404 412 _ip.magic("xdel a")
405 413
406 414 # Check that a's __del__ method has been called.
407 415 nt.assert_equal(monitor, [1])
408 416
409 417 def doctest_who():
410 418 """doctest for %who
411 419
412 420 In [1]: %reset -f
413 421
414 422 In [2]: alpha = 123
415 423
416 424 In [3]: beta = 'beta'
417 425
418 426 In [4]: %who int
419 427 alpha
420 428
421 429 In [5]: %who str
422 430 beta
423 431
424 432 In [6]: %whos
425 433 Variable Type Data/Info
426 434 ----------------------------
427 435 alpha int 123
428 436 beta str beta
429 437
430 438 In [7]: %who_ls
431 439 Out[7]: ['alpha', 'beta']
432 440 """
433 441
434 442 @py3compat.u_format
435 443 def doctest_precision():
436 444 """doctest for %precision
437 445
438 446 In [1]: f = get_ipython().shell.display_formatter.formatters['text/plain']
439 447
440 448 In [2]: %precision 5
441 449 Out[2]: {u}'%.5f'
442 450
443 451 In [3]: f.float_format
444 452 Out[3]: {u}'%.5f'
445 453
446 454 In [4]: %precision %e
447 455 Out[4]: {u}'%e'
448 456
449 457 In [5]: f(3.1415927)
450 458 Out[5]: {u}'3.141593e+00'
451 459 """
452 460
@@ -1,210 +1,218 b''
1 1 """Tests for code execution (%run and related), which is particularly tricky.
2 2
3 3 Because of how %run manages namespaces, and the fact that we are trying here to
4 4 verify subtle object deletion and reference counting issues, the %run tests
5 5 will be kept in this separate file. This makes it easier to aggregate in one
6 6 place the tricks needed to handle it; most other magics are much easier to test
7 7 and we do so in a common test_magic file.
8 8 """
9 9 from __future__ import absolute_import
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Imports
13 13 #-----------------------------------------------------------------------------
14 14
15 15 import os
16 16 import sys
17 17 import tempfile
18 18
19 19 import nose.tools as nt
20 20 from nose import SkipTest
21 21
22 22 from IPython.testing import decorators as dec
23 23 from IPython.testing import tools as tt
24 24 from IPython.utils import py3compat
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Test functions begin
28 28 #-----------------------------------------------------------------------------
29 29
30 30 def doctest_refbug():
31 31 """Very nasty problem with references held by multiple runs of a script.
32 32 See: https://github.com/ipython/ipython/issues/141
33 33
34 34 In [1]: _ip.clear_main_mod_cache()
35 35 # random
36 36
37 37 In [2]: %run refbug
38 38
39 39 In [3]: call_f()
40 40 lowercased: hello
41 41
42 42 In [4]: %run refbug
43 43
44 44 In [5]: call_f()
45 45 lowercased: hello
46 46 lowercased: hello
47 47 """
48 48
49 49
50 50 def doctest_run_builtins():
51 51 r"""Check that %run doesn't damage __builtins__.
52 52
53 53 In [1]: import tempfile
54 54
55 55 In [2]: bid1 = id(__builtins__)
56 56
57 57 In [3]: fname = tempfile.mkstemp('.py')[1]
58 58
59 59 In [3]: f = open(fname,'w')
60 60
61 61 In [4]: dummy= f.write('pass\n')
62 62
63 63 In [5]: f.flush()
64 64
65 65 In [6]: t1 = type(__builtins__)
66 66
67 67 In [7]: %run $fname
68 68
69 69 In [7]: f.close()
70 70
71 71 In [8]: bid2 = id(__builtins__)
72 72
73 73 In [9]: t2 = type(__builtins__)
74 74
75 75 In [10]: t1 == t2
76 76 Out[10]: True
77 77
78 78 In [10]: bid1 == bid2
79 79 Out[10]: True
80 80
81 81 In [12]: try:
82 82 ....: os.unlink(fname)
83 83 ....: except:
84 84 ....: pass
85 85 ....:
86 86 """
87 87
88 88 @py3compat.doctest_refactor_print
89 89 def doctest_reset_del():
90 90 """Test that resetting doesn't cause errors in __del__ methods.
91 91
92 92 In [2]: class A(object):
93 93 ...: def __del__(self):
94 94 ...: print str("Hi")
95 95 ...:
96 96
97 97 In [3]: a = A()
98 98
99 99 In [4]: get_ipython().reset()
100 100 Hi
101 101
102 102 In [5]: 1+1
103 103 Out[5]: 2
104 104 """
105 105
106 106 # For some tests, it will be handy to organize them in a class with a common
107 107 # setup that makes a temp file
108 108
109 109 class TestMagicRunPass(tt.TempFileMixin):
110 110
111 111 def setup(self):
112 112 """Make a valid python temp file."""
113 113 self.mktmp('pass\n')
114 114
115 115 def run_tmpfile(self):
116 116 _ip = get_ipython()
117 117 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
118 118 # See below and ticket https://bugs.launchpad.net/bugs/366353
119 119 _ip.magic('run %s' % self.fname)
120 120
121 121 def test_builtins_id(self):
122 122 """Check that %run doesn't damage __builtins__ """
123 123 _ip = get_ipython()
124 124 # Test that the id of __builtins__ is not modified by %run
125 125 bid1 = id(_ip.user_ns['__builtins__'])
126 126 self.run_tmpfile()
127 127 bid2 = id(_ip.user_ns['__builtins__'])
128 128 tt.assert_equals(bid1, bid2)
129 129
130 130 def test_builtins_type(self):
131 131 """Check that the type of __builtins__ doesn't change with %run.
132 132
133 133 However, the above could pass if __builtins__ was already modified to
134 134 be a dict (it should be a module) by a previous use of %run. So we
135 135 also check explicitly that it really is a module:
136 136 """
137 137 _ip = get_ipython()
138 138 self.run_tmpfile()
139 139 tt.assert_equals(type(_ip.user_ns['__builtins__']),type(sys))
140 140
141 141 def test_prompts(self):
142 142 """Test that prompts correctly generate after %run"""
143 143 self.run_tmpfile()
144 144 _ip = get_ipython()
145 145 p2 = str(_ip.displayhook.prompt2).strip()
146 146 nt.assert_equals(p2[:3], '...')
147 147
148 148
149 149 class TestMagicRunSimple(tt.TempFileMixin):
150 150
151 151 def test_simpledef(self):
152 152 """Test that simple class definitions work."""
153 153 src = ("class foo: pass\n"
154 154 "def f(): return foo()")
155 155 self.mktmp(src)
156 156 _ip.magic('run %s' % self.fname)
157 157 _ip.run_cell('t = isinstance(f(), foo)')
158 158 nt.assert_true(_ip.user_ns['t'])
159 159
160 160 def test_obj_del(self):
161 161 """Test that object's __del__ methods are called on exit."""
162 162 if sys.platform == 'win32':
163 163 try:
164 164 import win32api
165 165 except ImportError:
166 166 raise SkipTest("Test requires pywin32")
167 167 src = ("class A(object):\n"
168 168 " def __del__(self):\n"
169 169 " print 'object A deleted'\n"
170 170 "a = A()\n")
171 171 self.mktmp(py3compat.doctest_refactor_print(src))
172 tt.ipexec_validate(self.fname, 'object A deleted')
173
174 @dec.skip_known_failure
172 if dec.module_not_available('sqlite3'):
173 err = 'WARNING: IPython History requires SQLite, your history will not be saved\n'
174 else:
175 err = None
176 tt.ipexec_validate(self.fname, 'object A deleted', err)
177
178 @dec.skip_known_failure
175 179 def test_aggressive_namespace_cleanup(self):
176 180 """Test that namespace cleanup is not too aggressive GH-238
177 181
178 182 Returning from another run magic deletes the namespace"""
179 183 # see ticket https://github.com/ipython/ipython/issues/238
180 184 class secondtmp(tt.TempFileMixin): pass
181 185 empty = secondtmp()
182 186 empty.mktmp('')
183 187 src = ("ip = get_ipython()\n"
184 188 "for i in range(5):\n"
185 189 " try:\n"
186 190 " ip.magic('run %s')\n"
187 191 " except NameError, e:\n"
188 192 " print i;break\n" % empty.fname)
189 193 self.mktmp(py3compat.doctest_refactor_print(src))
190 194 _ip.magic('run %s' % self.fname)
191 195 _ip.run_cell('ip == get_ipython()')
192 196 tt.assert_equals(_ip.user_ns['i'], 5)
193 197
194 198 @dec.skip_win32
195 199 def test_tclass(self):
196 200 mydir = os.path.dirname(__file__)
197 201 tc = os.path.join(mydir, 'tclass')
198 202 src = ("%%run '%s' C-first\n"
199 203 "%%run '%s' C-second\n"
200 204 "%%run '%s' C-third\n") % (tc, tc, tc)
201 205 self.mktmp(src, '.ipy')
202 206 out = """\
203 207 ARGV 1-: ['C-first']
204 208 ARGV 1-: ['C-second']
205 209 tclass.py: deleting object: C-first
206 210 ARGV 1-: ['C-third']
207 211 tclass.py: deleting object: C-second
208 212 tclass.py: deleting object: C-third
209 213 """
210 tt.ipexec_validate(self.fname, out)
214 if dec.module_not_available('sqlite3'):
215 err = 'WARNING: IPython History requires SQLite, your history will not be saved\n'
216 else:
217 err = None
218 tt.ipexec_validate(self.fname, out, err)
@@ -1,400 +1,408 b''
1 1 """A TaskRecord backend using sqlite3
2 2
3 3 Authors:
4 4
5 5 * Min RK
6 6 """
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2011 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 import json
15 15 import os
16 16 import cPickle as pickle
17 17 from datetime import datetime
18 18
19 import sqlite3
19 try:
20 import sqlite3
21 except ImportError:
22 sqlite3 = None
20 23
21 24 from zmq.eventloop import ioloop
22 25
23 26 from IPython.utils.traitlets import Unicode, Instance, List, Dict
24 27 from .dictdb import BaseDB
25 28 from IPython.utils.jsonutil import date_default, extract_dates, squash_dates
26 29
27 30 #-----------------------------------------------------------------------------
28 31 # SQLite operators, adapters, and converters
29 32 #-----------------------------------------------------------------------------
30 33
31 34 try:
32 35 buffer
33 36 except NameError:
34 37 # py3k
35 38 buffer = memoryview
36 39
37 40 operators = {
38 41 '$lt' : "<",
39 42 '$gt' : ">",
40 43 # null is handled weird with ==,!=
41 44 '$eq' : "=",
42 45 '$ne' : "!=",
43 46 '$lte': "<=",
44 47 '$gte': ">=",
45 48 '$in' : ('=', ' OR '),
46 49 '$nin': ('!=', ' AND '),
47 50 # '$all': None,
48 51 # '$mod': None,
49 52 # '$exists' : None
50 53 }
51 54 null_operators = {
52 55 '=' : "IS NULL",
53 56 '!=' : "IS NOT NULL",
54 57 }
55 58
56 59 def _adapt_dict(d):
57 60 return json.dumps(d, default=date_default)
58 61
59 62 def _convert_dict(ds):
60 63 if ds is None:
61 64 return ds
62 65 else:
63 66 if isinstance(ds, bytes):
64 67 # If I understand the sqlite doc correctly, this will always be utf8
65 68 ds = ds.decode('utf8')
66 69 return extract_dates(json.loads(ds))
67 70
68 71 def _adapt_bufs(bufs):
69 72 # this is *horrible*
70 73 # copy buffers into single list and pickle it:
71 74 if bufs and isinstance(bufs[0], (bytes, buffer)):
72 75 return sqlite3.Binary(pickle.dumps(map(bytes, bufs),-1))
73 76 elif bufs:
74 77 return bufs
75 78 else:
76 79 return None
77 80
78 81 def _convert_bufs(bs):
79 82 if bs is None:
80 83 return []
81 84 else:
82 85 return pickle.loads(bytes(bs))
83 86
84 87 #-----------------------------------------------------------------------------
85 88 # SQLiteDB class
86 89 #-----------------------------------------------------------------------------
87 90
88 91 class SQLiteDB(BaseDB):
89 92 """SQLite3 TaskRecord backend."""
90 93
91 94 filename = Unicode('tasks.db', config=True,
92 95 help="""The filename of the sqlite task database. [default: 'tasks.db']""")
93 96 location = Unicode('', config=True,
94 97 help="""The directory containing the sqlite task database. The default
95 98 is to use the cluster_dir location.""")
96 99 table = Unicode("", config=True,
97 100 help="""The SQLite Table to use for storing tasks for this session. If unspecified,
98 101 a new table will be created with the Hub's IDENT. Specifying the table will result
99 102 in tasks from previous sessions being available via Clients' db_query and
100 103 get_result methods.""")
101 104
102 _db = Instance('sqlite3.Connection')
105 if sqlite3 is not None:
106 _db = Instance('sqlite3.Connection')
107 else:
108 _db = None
103 109 # the ordered list of column names
104 110 _keys = List(['msg_id' ,
105 111 'header' ,
106 112 'content',
107 113 'buffers',
108 114 'submitted',
109 115 'client_uuid' ,
110 116 'engine_uuid' ,
111 117 'started',
112 118 'completed',
113 119 'resubmitted',
114 120 'result_header' ,
115 121 'result_content' ,
116 122 'result_buffers' ,
117 123 'queue' ,
118 124 'pyin' ,
119 125 'pyout',
120 126 'pyerr',
121 127 'stdout',
122 128 'stderr',
123 129 ])
124 130 # sqlite datatypes for checking that db is current format
125 131 _types = Dict({'msg_id' : 'text' ,
126 132 'header' : 'dict text',
127 133 'content' : 'dict text',
128 134 'buffers' : 'bufs blob',
129 135 'submitted' : 'timestamp',
130 136 'client_uuid' : 'text',
131 137 'engine_uuid' : 'text',
132 138 'started' : 'timestamp',
133 139 'completed' : 'timestamp',
134 140 'resubmitted' : 'timestamp',
135 141 'result_header' : 'dict text',
136 142 'result_content' : 'dict text',
137 143 'result_buffers' : 'bufs blob',
138 144 'queue' : 'text',
139 145 'pyin' : 'text',
140 146 'pyout' : 'text',
141 147 'pyerr' : 'text',
142 148 'stdout' : 'text',
143 149 'stderr' : 'text',
144 150 })
145 151
146 152 def __init__(self, **kwargs):
147 153 super(SQLiteDB, self).__init__(**kwargs)
154 if sqlite3 is None:
155 raise ImportError("SQLiteDB requires sqlite3")
148 156 if not self.table:
149 157 # use session, and prefix _, since starting with # is illegal
150 158 self.table = '_'+self.session.replace('-','_')
151 159 if not self.location:
152 160 # get current profile
153 161 from IPython.core.application import BaseIPythonApplication
154 162 if BaseIPythonApplication.initialized():
155 163 app = BaseIPythonApplication.instance()
156 164 if app.profile_dir is not None:
157 165 self.location = app.profile_dir.location
158 166 else:
159 167 self.location = u'.'
160 168 else:
161 169 self.location = u'.'
162 170 self._init_db()
163 171
164 172 # register db commit as 2s periodic callback
165 173 # to prevent clogging pipes
166 174 # assumes we are being run in a zmq ioloop app
167 175 loop = ioloop.IOLoop.instance()
168 176 pc = ioloop.PeriodicCallback(self._db.commit, 2000, loop)
169 177 pc.start()
170 178
171 179 def _defaults(self, keys=None):
172 180 """create an empty record"""
173 181 d = {}
174 182 keys = self._keys if keys is None else keys
175 183 for key in keys:
176 184 d[key] = None
177 185 return d
178 186
179 187 def _check_table(self):
180 188 """Ensure that an incorrect table doesn't exist
181 189
182 190 If a bad (old) table does exist, return False
183 191 """
184 192 cursor = self._db.execute("PRAGMA table_info(%s)"%self.table)
185 193 lines = cursor.fetchall()
186 194 if not lines:
187 195 # table does not exist
188 196 return True
189 197 types = {}
190 198 keys = []
191 199 for line in lines:
192 200 keys.append(line[1])
193 201 types[line[1]] = line[2]
194 202 if self._keys != keys:
195 203 # key mismatch
196 204 self.log.warn('keys mismatch')
197 205 return False
198 206 for key in self._keys:
199 207 if types[key] != self._types[key]:
200 208 self.log.warn(
201 209 'type mismatch: %s: %s != %s'%(key,types[key],self._types[key])
202 210 )
203 211 return False
204 212 return True
205 213
206 214 def _init_db(self):
207 215 """Connect to the database and get new session number."""
208 216 # register adapters
209 217 sqlite3.register_adapter(dict, _adapt_dict)
210 218 sqlite3.register_converter('dict', _convert_dict)
211 219 sqlite3.register_adapter(list, _adapt_bufs)
212 220 sqlite3.register_converter('bufs', _convert_bufs)
213 221 # connect to the db
214 222 dbfile = os.path.join(self.location, self.filename)
215 223 self._db = sqlite3.connect(dbfile, detect_types=sqlite3.PARSE_DECLTYPES,
216 224 # isolation_level = None)#,
217 225 cached_statements=64)
218 226 # print dir(self._db)
219 227 first_table = self.table
220 228 i=0
221 229 while not self._check_table():
222 230 i+=1
223 231 self.table = first_table+'_%i'%i
224 232 self.log.warn(
225 233 "Table %s exists and doesn't match db format, trying %s"%
226 234 (first_table,self.table)
227 235 )
228 236
229 237 self._db.execute("""CREATE TABLE IF NOT EXISTS %s
230 238 (msg_id text PRIMARY KEY,
231 239 header dict text,
232 240 content dict text,
233 241 buffers bufs blob,
234 242 submitted timestamp,
235 243 client_uuid text,
236 244 engine_uuid text,
237 245 started timestamp,
238 246 completed timestamp,
239 247 resubmitted timestamp,
240 248 result_header dict text,
241 249 result_content dict text,
242 250 result_buffers bufs blob,
243 251 queue text,
244 252 pyin text,
245 253 pyout text,
246 254 pyerr text,
247 255 stdout text,
248 256 stderr text)
249 257 """%self.table)
250 258 self._db.commit()
251 259
252 260 def _dict_to_list(self, d):
253 261 """turn a mongodb-style record dict into a list."""
254 262
255 263 return [ d[key] for key in self._keys ]
256 264
257 265 def _list_to_dict(self, line, keys=None):
258 266 """Inverse of dict_to_list"""
259 267 keys = self._keys if keys is None else keys
260 268 d = self._defaults(keys)
261 269 for key,value in zip(keys, line):
262 270 d[key] = value
263 271
264 272 return d
265 273
266 274 def _render_expression(self, check):
267 275 """Turn a mongodb-style search dict into an SQL query."""
268 276 expressions = []
269 277 args = []
270 278
271 279 skeys = set(check.keys())
272 280 skeys.difference_update(set(self._keys))
273 281 skeys.difference_update(set(['buffers', 'result_buffers']))
274 282 if skeys:
275 283 raise KeyError("Illegal testing key(s): %s"%skeys)
276 284
277 285 for name,sub_check in check.iteritems():
278 286 if isinstance(sub_check, dict):
279 287 for test,value in sub_check.iteritems():
280 288 try:
281 289 op = operators[test]
282 290 except KeyError:
283 291 raise KeyError("Unsupported operator: %r"%test)
284 292 if isinstance(op, tuple):
285 293 op, join = op
286 294
287 295 if value is None and op in null_operators:
288 296 expr = "%s %s"%null_operators[op]
289 297 else:
290 298 expr = "%s %s ?"%(name, op)
291 299 if isinstance(value, (tuple,list)):
292 300 if op in null_operators and any([v is None for v in value]):
293 301 # equality tests don't work with NULL
294 302 raise ValueError("Cannot use %r test with NULL values on SQLite backend"%test)
295 303 expr = '( %s )'%( join.join([expr]*len(value)) )
296 304 args.extend(value)
297 305 else:
298 306 args.append(value)
299 307 expressions.append(expr)
300 308 else:
301 309 # it's an equality check
302 310 if sub_check is None:
303 311 expressions.append("%s IS NULL")
304 312 else:
305 313 expressions.append("%s = ?"%name)
306 314 args.append(sub_check)
307 315
308 316 expr = " AND ".join(expressions)
309 317 return expr, args
310 318
311 319 def add_record(self, msg_id, rec):
312 320 """Add a new Task Record, by msg_id."""
313 321 d = self._defaults()
314 322 d.update(rec)
315 323 d['msg_id'] = msg_id
316 324 line = self._dict_to_list(d)
317 325 tups = '(%s)'%(','.join(['?']*len(line)))
318 326 self._db.execute("INSERT INTO %s VALUES %s"%(self.table, tups), line)
319 327 # self._db.commit()
320 328
321 329 def get_record(self, msg_id):
322 330 """Get a specific Task Record, by msg_id."""
323 331 cursor = self._db.execute("""SELECT * FROM %s WHERE msg_id==?"""%self.table, (msg_id,))
324 332 line = cursor.fetchone()
325 333 if line is None:
326 334 raise KeyError("No such msg: %r"%msg_id)
327 335 return self._list_to_dict(line)
328 336
329 337 def update_record(self, msg_id, rec):
330 338 """Update the data in an existing record."""
331 339 query = "UPDATE %s SET "%self.table
332 340 sets = []
333 341 keys = sorted(rec.keys())
334 342 values = []
335 343 for key in keys:
336 344 sets.append('%s = ?'%key)
337 345 values.append(rec[key])
338 346 query += ', '.join(sets)
339 347 query += ' WHERE msg_id == ?'
340 348 values.append(msg_id)
341 349 self._db.execute(query, values)
342 350 # self._db.commit()
343 351
344 352 def drop_record(self, msg_id):
345 353 """Remove a record from the DB."""
346 354 self._db.execute("""DELETE FROM %s WHERE msg_id==?"""%self.table, (msg_id,))
347 355 # self._db.commit()
348 356
349 357 def drop_matching_records(self, check):
350 358 """Remove a record from the DB."""
351 359 expr,args = self._render_expression(check)
352 360 query = "DELETE FROM %s WHERE %s"%(self.table, expr)
353 361 self._db.execute(query,args)
354 362 # self._db.commit()
355 363
356 364 def find_records(self, check, keys=None):
357 365 """Find records matching a query dict, optionally extracting subset of keys.
358 366
359 367 Returns list of matching records.
360 368
361 369 Parameters
362 370 ----------
363 371
364 372 check: dict
365 373 mongodb-style query argument
366 374 keys: list of strs [optional]
367 375 if specified, the subset of keys to extract. msg_id will *always* be
368 376 included.
369 377 """
370 378 if keys:
371 379 bad_keys = [ key for key in keys if key not in self._keys ]
372 380 if bad_keys:
373 381 raise KeyError("Bad record key(s): %s"%bad_keys)
374 382
375 383 if keys:
376 384 # ensure msg_id is present and first:
377 385 if 'msg_id' in keys:
378 386 keys.remove('msg_id')
379 387 keys.insert(0, 'msg_id')
380 388 req = ', '.join(keys)
381 389 else:
382 390 req = '*'
383 391 expr,args = self._render_expression(check)
384 392 query = """SELECT %s FROM %s WHERE %s"""%(req, self.table, expr)
385 393 cursor = self._db.execute(query, args)
386 394 matches = cursor.fetchall()
387 395 records = []
388 396 for line in matches:
389 397 rec = self._list_to_dict(line, keys)
390 398 records.append(rec)
391 399 return records
392 400
393 401 def get_history(self):
394 402 """get all msg_ids, ordered by time submitted."""
395 403 query = """SELECT msg_id FROM %s ORDER by submitted ASC"""%self.table
396 404 cursor = self._db.execute(query)
397 405 # will be a list of length 1 tuples
398 406 return [ tup[0] for tup in cursor.fetchall()]
399 407
400 408 __all__ = ['SQLiteDB'] No newline at end of file
@@ -1,180 +1,182 b''
1 1 """Tests for db backends
2 2
3 3 Authors:
4 4
5 5 * Min RK
6 6 """
7 7
8 8 #-------------------------------------------------------------------------------
9 9 # Copyright (C) 2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-------------------------------------------------------------------------------
14 14
15 15 #-------------------------------------------------------------------------------
16 16 # Imports
17 17 #-------------------------------------------------------------------------------
18 18
19 19 from __future__ import division
20 20
21 21 import tempfile
22 22 import time
23 23
24 24 from datetime import datetime, timedelta
25 25 from unittest import TestCase
26 26
27 from nose import SkipTest
28
29 27 from IPython.parallel import error
30 28 from IPython.parallel.controller.dictdb import DictDB
31 29 from IPython.parallel.controller.sqlitedb import SQLiteDB
32 30 from IPython.parallel.controller.hub import init_record, empty_record
33 31
32 from IPython.testing import decorators as dec
34 33 from IPython.zmq.session import Session
35 34
36 35
37 36 #-------------------------------------------------------------------------------
38 37 # TestCases
39 38 #-------------------------------------------------------------------------------
40 39
41 40 class TestDictBackend(TestCase):
42 41 def setUp(self):
43 42 self.session = Session()
44 43 self.db = self.create_db()
45 44 self.load_records(16)
46 45
47 46 def create_db(self):
48 47 return DictDB()
49 48
50 49 def load_records(self, n=1):
51 50 """load n records for testing"""
52 51 #sleep 1/10 s, to ensure timestamp is different to previous calls
53 52 time.sleep(0.1)
54 53 msg_ids = []
55 54 for i in range(n):
56 55 msg = self.session.msg('apply_request', content=dict(a=5))
57 56 msg['buffers'] = []
58 57 rec = init_record(msg)
59 58 msg_id = msg['header']['msg_id']
60 59 msg_ids.append(msg_id)
61 60 self.db.add_record(msg_id, rec)
62 61 return msg_ids
63 62
64 63 def test_add_record(self):
65 64 before = self.db.get_history()
66 65 self.load_records(5)
67 66 after = self.db.get_history()
68 67 self.assertEquals(len(after), len(before)+5)
69 68 self.assertEquals(after[:-5],before)
70 69
71 70 def test_drop_record(self):
72 71 msg_id = self.load_records()[-1]
73 72 rec = self.db.get_record(msg_id)
74 73 self.db.drop_record(msg_id)
75 74 self.assertRaises(KeyError,self.db.get_record, msg_id)
76 75
77 76 def _round_to_millisecond(self, dt):
78 77 """necessary because mongodb rounds microseconds"""
79 78 micro = dt.microsecond
80 79 extra = int(str(micro)[-3:])
81 80 return dt - timedelta(microseconds=extra)
82 81
83 82 def test_update_record(self):
84 83 now = self._round_to_millisecond(datetime.now())
85 84 #
86 85 msg_id = self.db.get_history()[-1]
87 86 rec1 = self.db.get_record(msg_id)
88 87 data = {'stdout': 'hello there', 'completed' : now}
89 88 self.db.update_record(msg_id, data)
90 89 rec2 = self.db.get_record(msg_id)
91 90 self.assertEquals(rec2['stdout'], 'hello there')
92 91 self.assertEquals(rec2['completed'], now)
93 92 rec1.update(data)
94 93 self.assertEquals(rec1, rec2)
95 94
96 95 # def test_update_record_bad(self):
97 96 # """test updating nonexistant records"""
98 97 # msg_id = str(uuid.uuid4())
99 98 # data = {'stdout': 'hello there'}
100 99 # self.assertRaises(KeyError, self.db.update_record, msg_id, data)
101 100
102 101 def test_find_records_dt(self):
103 102 """test finding records by date"""
104 103 hist = self.db.get_history()
105 104 middle = self.db.get_record(hist[len(hist)//2])
106 105 tic = middle['submitted']
107 106 before = self.db.find_records({'submitted' : {'$lt' : tic}})
108 107 after = self.db.find_records({'submitted' : {'$gte' : tic}})
109 108 self.assertEquals(len(before)+len(after),len(hist))
110 109 for b in before:
111 110 self.assertTrue(b['submitted'] < tic)
112 111 for a in after:
113 112 self.assertTrue(a['submitted'] >= tic)
114 113 same = self.db.find_records({'submitted' : tic})
115 114 for s in same:
116 115 self.assertTrue(s['submitted'] == tic)
117 116
118 117 def test_find_records_keys(self):
119 118 """test extracting subset of record keys"""
120 119 found = self.db.find_records({'msg_id': {'$ne' : ''}},keys=['submitted', 'completed'])
121 120 for rec in found:
122 121 self.assertEquals(set(rec.keys()), set(['msg_id', 'submitted', 'completed']))
123 122
124 123 def test_find_records_msg_id(self):
125 124 """ensure msg_id is always in found records"""
126 125 found = self.db.find_records({'msg_id': {'$ne' : ''}},keys=['submitted', 'completed'])
127 126 for rec in found:
128 127 self.assertTrue('msg_id' in rec.keys())
129 128 found = self.db.find_records({'msg_id': {'$ne' : ''}},keys=['submitted'])
130 129 for rec in found:
131 130 self.assertTrue('msg_id' in rec.keys())
132 131 found = self.db.find_records({'msg_id': {'$ne' : ''}},keys=['msg_id'])
133 132 for rec in found:
134 133 self.assertTrue('msg_id' in rec.keys())
135 134
136 135 def test_find_records_in(self):
137 136 """test finding records with '$in','$nin' operators"""
138 137 hist = self.db.get_history()
139 138 even = hist[::2]
140 139 odd = hist[1::2]
141 140 recs = self.db.find_records({ 'msg_id' : {'$in' : even}})
142 141 found = [ r['msg_id'] for r in recs ]
143 142 self.assertEquals(set(even), set(found))
144 143 recs = self.db.find_records({ 'msg_id' : {'$nin' : even}})
145 144 found = [ r['msg_id'] for r in recs ]
146 145 self.assertEquals(set(odd), set(found))
147 146
148 147 def test_get_history(self):
149 148 msg_ids = self.db.get_history()
150 149 latest = datetime(1984,1,1)
151 150 for msg_id in msg_ids:
152 151 rec = self.db.get_record(msg_id)
153 152 newt = rec['submitted']
154 153 self.assertTrue(newt >= latest)
155 154 latest = newt
156 155 msg_id = self.load_records(1)[-1]
157 156 self.assertEquals(self.db.get_history()[-1],msg_id)
158 157
159 158 def test_datetime(self):
160 159 """get/set timestamps with datetime objects"""
161 160 msg_id = self.db.get_history()[-1]
162 161 rec = self.db.get_record(msg_id)
163 162 self.assertTrue(isinstance(rec['submitted'], datetime))
164 163 self.db.update_record(msg_id, dict(completed=datetime.now()))
165 164 rec = self.db.get_record(msg_id)
166 165 self.assertTrue(isinstance(rec['completed'], datetime))
167 166
168 167 def test_drop_matching(self):
169 168 msg_ids = self.load_records(10)
170 169 query = {'msg_id' : {'$in':msg_ids}}
171 170 self.db.drop_matching_records(query)
172 171 recs = self.db.find_records(query)
173 172 self.assertEquals(len(recs), 0)
174
173
174
175 175 class TestSQLiteBackend(TestDictBackend):
176
177 @dec.skip_without('sqlite3')
176 178 def create_db(self):
177 179 return SQLiteDB(location=tempfile.gettempdir())
178 180
179 181 def tearDown(self):
180 182 self.db._db.close()
@@ -1,473 +1,476 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) 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 """
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Copyright (C) 2009 The IPython Development Team
19 19 #
20 20 # Distributed under the terms of the BSD License. The full license is in
21 21 # the file COPYING, distributed as part of this software.
22 22 #-----------------------------------------------------------------------------
23 23
24 24 #-----------------------------------------------------------------------------
25 25 # Imports
26 26 #-----------------------------------------------------------------------------
27 27
28 28 # Stdlib
29 29 import os
30 30 import os.path as path
31 31 import signal
32 32 import sys
33 33 import subprocess
34 34 import tempfile
35 35 import time
36 36 import warnings
37 37
38 38 # Note: monkeypatch!
39 39 # We need to monkeypatch a small problem in nose itself first, before importing
40 40 # it for actual use. This should get into nose upstream, but its release cycle
41 41 # is slow and we need it for our parametric tests to work correctly.
42 42 from IPython.testing import nosepatch
43 43 # Now, proceed to import nose itself
44 44 import nose.plugins.builtin
45 45 from nose.core import TestProgram
46 46
47 47 # Our own imports
48 48 from IPython.utils.importstring import import_item
49 49 from IPython.utils.path import get_ipython_module_path
50 50 from IPython.utils.process import find_cmd, pycmd2argv
51 51 from IPython.utils.sysinfo import sys_info
52 52
53 53 from IPython.testing import globalipapp
54 54 from IPython.testing.plugin.ipdoctest import IPythonDoctest
55 55 from IPython.external.decorators import KnownFailure
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 def extract_version(mod):
86 86 return mod.__version__
87 87
88 88 def test_for(item, min_version=None, callback=extract_version):
89 89 """Test to see if item is importable, and optionally check against a minimum
90 90 version.
91 91
92 92 If min_version is given, the default behavior is to check against the
93 93 `__version__` attribute of the item, but specifying `callback` allows you to
94 94 extract the value you are interested in. e.g::
95 95
96 96 In [1]: import sys
97 97
98 98 In [2]: from IPython.testing.iptest import test_for
99 99
100 100 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
101 101 Out[3]: True
102 102
103 103 """
104 104 try:
105 105 check = import_item(item)
106 106 except (ImportError, RuntimeError):
107 107 # GTK reports Runtime error if it can't be initialized even if it's
108 108 # importable.
109 109 return False
110 110 else:
111 111 if min_version:
112 112 if callback:
113 113 # extra processing step to get version to compare
114 114 check = callback(check)
115 115
116 116 return check >= min_version
117 117 else:
118 118 return True
119 119
120 120 # Global dict where we can store information on what we have and what we don't
121 121 # have available at test run time
122 122 have = {}
123 123
124 124 have['curses'] = test_for('_curses')
125 125 have['matplotlib'] = test_for('matplotlib')
126 126 have['pexpect'] = test_for('IPython.external.pexpect')
127 127 have['pymongo'] = test_for('pymongo')
128 128 have['wx'] = test_for('wx')
129 129 have['wx.aui'] = test_for('wx.aui')
130 130 have['qt'] = test_for('IPython.external.qt')
131 have['sqlite3'] = test_for('sqlite3')
131 132
132 133 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
133 134
134 135 if os.name == 'nt':
135 136 min_zmq = (2,1,7)
136 137 else:
137 138 min_zmq = (2,1,4)
138 139
139 140 def version_tuple(mod):
140 141 "turn '2.1.9' into (2,1,9), and '2.1dev' into (2,1,999)"
141 142 # turn 'dev' into 999, because Python3 rejects str-int comparisons
142 143 vs = mod.__version__.replace('dev', '.999')
143 144 tup = tuple([int(v) for v in vs.split('.') ])
144 145 return tup
145 146
146 147 have['zmq'] = test_for('zmq', min_zmq, version_tuple)
147 148
148 149 #-----------------------------------------------------------------------------
149 150 # Functions and classes
150 151 #-----------------------------------------------------------------------------
151 152
152 153 def report():
153 154 """Return a string with a summary report of test-related variables."""
154 155
155 156 out = [ sys_info(), '\n']
156 157
157 158 avail = []
158 159 not_avail = []
159 160
160 161 for k, is_avail in have.items():
161 162 if is_avail:
162 163 avail.append(k)
163 164 else:
164 165 not_avail.append(k)
165 166
166 167 if avail:
167 168 out.append('\nTools and libraries available at test time:\n')
168 169 avail.sort()
169 170 out.append(' ' + ' '.join(avail)+'\n')
170 171
171 172 if not_avail:
172 173 out.append('\nTools and libraries NOT available at test time:\n')
173 174 not_avail.sort()
174 175 out.append(' ' + ' '.join(not_avail)+'\n')
175 176
176 177 return ''.join(out)
177 178
178 179
179 180 def make_exclude():
180 181 """Make patterns of modules and packages to exclude from testing.
181 182
182 183 For the IPythonDoctest plugin, we need to exclude certain patterns that
183 184 cause testing problems. We should strive to minimize the number of
184 185 skipped modules, since this means untested code.
185 186
186 187 These modules and packages will NOT get scanned by nose at all for tests.
187 188 """
188 189 # Simple utility to make IPython paths more readably, we need a lot of
189 190 # these below
190 191 ipjoin = lambda *paths: pjoin('IPython', *paths)
191 192
192 193 exclusions = [ipjoin('external'),
193 194 pjoin('IPython_doctest_plugin'),
194 195 ipjoin('quarantine'),
195 196 ipjoin('deathrow'),
196 197 ipjoin('testing', 'attic'),
197 198 # This guy is probably attic material
198 199 ipjoin('testing', 'mkdoctests'),
199 200 # Testing inputhook will need a lot of thought, to figure out
200 201 # how to have tests that don't lock up with the gui event
201 202 # loops in the picture
202 203 ipjoin('lib', 'inputhook'),
203 204 # Config files aren't really importable stand-alone
204 205 ipjoin('config', 'default'),
205 206 ipjoin('config', 'profile'),
206 207 ]
207
208 if not have['sqlite3']:
209 exclusions.append(ipjoin('core', 'tests', 'test_history'))
210 exclusions.append(ipjoin('core', 'history'))
208 211 if not have['wx']:
209 212 exclusions.append(ipjoin('lib', 'inputhookwx'))
210 213
211 214 # We do this unconditionally, so that the test suite doesn't import
212 215 # gtk, changing the default encoding and masking some unicode bugs.
213 216 exclusions.append(ipjoin('lib', 'inputhookgtk'))
214 217
215 218 # These have to be skipped on win32 because the use echo, rm, cd, etc.
216 219 # See ticket https://github.com/ipython/ipython/issues/87
217 220 if sys.platform == 'win32':
218 221 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
219 222 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
220 223
221 224 if not have['pexpect']:
222 225 exclusions.extend([ipjoin('scripts', 'irunner'),
223 226 ipjoin('lib', 'irunner'),
224 227 ipjoin('lib', 'tests', 'test_irunner')])
225 228
226 229 if not have['zmq']:
227 230 exclusions.append(ipjoin('zmq'))
228 231 exclusions.append(ipjoin('frontend', 'qt'))
229 232 exclusions.append(ipjoin('parallel'))
230 233 elif not have['qt']:
231 234 exclusions.append(ipjoin('frontend', 'qt'))
232 235
233 236 if not have['pymongo']:
234 237 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
235 238 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
236 239
237 240 if not have['matplotlib']:
238 241 exclusions.extend([ipjoin('lib', 'pylabtools'),
239 242 ipjoin('lib', 'tests', 'test_pylabtools')])
240 243
241 244 if not have['tornado']:
242 245 exclusions.append(ipjoin('frontend', 'html'))
243 246
244 247 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
245 248 if sys.platform == 'win32':
246 249 exclusions = [s.replace('\\','\\\\') for s in exclusions]
247 250
248 251 return exclusions
249 252
250 253
251 254 class IPTester(object):
252 255 """Call that calls iptest or trial in a subprocess.
253 256 """
254 257 #: string, name of test runner that will be called
255 258 runner = None
256 259 #: list, parameters for test runner
257 260 params = None
258 261 #: list, arguments of system call to be made to call test runner
259 262 call_args = None
260 263 #: list, process ids of subprocesses we start (for cleanup)
261 264 pids = None
262 265
263 266 def __init__(self, runner='iptest', params=None):
264 267 """Create new test runner."""
265 268 p = os.path
266 269 if runner == 'iptest':
267 270 iptest_app = get_ipython_module_path('IPython.testing.iptest')
268 271 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
269 272 else:
270 273 raise Exception('Not a valid test runner: %s' % repr(runner))
271 274 if params is None:
272 275 params = []
273 276 if isinstance(params, str):
274 277 params = [params]
275 278 self.params = params
276 279
277 280 # Assemble call
278 281 self.call_args = self.runner+self.params
279 282
280 283 # Store pids of anything we start to clean up on deletion, if possible
281 284 # (on posix only, since win32 has no os.kill)
282 285 self.pids = []
283 286
284 287 if sys.platform == 'win32':
285 288 def _run_cmd(self):
286 289 # On Windows, use os.system instead of subprocess.call, because I
287 290 # was having problems with subprocess and I just don't know enough
288 291 # about win32 to debug this reliably. Os.system may be the 'old
289 292 # fashioned' way to do it, but it works just fine. If someone
290 293 # later can clean this up that's fine, as long as the tests run
291 294 # reliably in win32.
292 295 # What types of problems are you having. They may be related to
293 296 # running Python in unboffered mode. BG.
294 297 return os.system(' '.join(self.call_args))
295 298 else:
296 299 def _run_cmd(self):
297 300 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
298 301 subp = subprocess.Popen(self.call_args)
299 302 self.pids.append(subp.pid)
300 303 # If this fails, the pid will be left in self.pids and cleaned up
301 304 # later, but if the wait call succeeds, then we can clear the
302 305 # stored pid.
303 306 retcode = subp.wait()
304 307 self.pids.pop()
305 308 return retcode
306 309
307 310 def run(self):
308 311 """Run the stored commands"""
309 312 try:
310 313 return self._run_cmd()
311 314 except:
312 315 import traceback
313 316 traceback.print_exc()
314 317 return 1 # signal failure
315 318
316 319 def __del__(self):
317 320 """Cleanup on exit by killing any leftover processes."""
318 321
319 322 if not hasattr(os, 'kill'):
320 323 return
321 324
322 325 for pid in self.pids:
323 326 try:
324 327 print 'Cleaning stale PID:', pid
325 328 os.kill(pid, signal.SIGKILL)
326 329 except OSError:
327 330 # This is just a best effort, if we fail or the process was
328 331 # really gone, ignore it.
329 332 pass
330 333
331 334
332 335 def make_runners():
333 336 """Define the top-level packages that need to be tested.
334 337 """
335 338
336 339 # Packages to be tested via nose, that only depend on the stdlib
337 340 nose_pkg_names = ['config', 'core', 'extensions', 'frontend', 'lib',
338 341 'scripts', 'testing', 'utils', 'nbformat' ]
339 342
340 343 if have['zmq']:
341 344 nose_pkg_names.append('parallel')
342 345
343 346 # For debugging this code, only load quick stuff
344 347 #nose_pkg_names = ['core', 'extensions'] # dbg
345 348
346 349 # Make fully qualified package names prepending 'IPython.' to our name lists
347 350 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
348 351
349 352 # Make runners
350 353 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
351 354
352 355 return runners
353 356
354 357
355 358 def run_iptest():
356 359 """Run the IPython test suite using nose.
357 360
358 361 This function is called when this script is **not** called with the form
359 362 `iptest all`. It simply calls nose with appropriate command line flags
360 363 and accepts all of the standard nose arguments.
361 364 """
362 365
363 366 warnings.filterwarnings('ignore',
364 367 'This will be removed soon. Use IPython.testing.util instead')
365 368
366 369 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
367 370
368 371 # Loading ipdoctest causes problems with Twisted, but
369 372 # our test suite runner now separates things and runs
370 373 # all Twisted tests with trial.
371 374 '--with-ipdoctest',
372 375 '--ipdoctest-tests','--ipdoctest-extension=txt',
373 376
374 377 # We add --exe because of setuptools' imbecility (it
375 378 # blindly does chmod +x on ALL files). Nose does the
376 379 # right thing and it tries to avoid executables,
377 380 # setuptools unfortunately forces our hand here. This
378 381 # has been discussed on the distutils list and the
379 382 # setuptools devs refuse to fix this problem!
380 383 '--exe',
381 384 ]
382 385
383 386 if nose.__version__ >= '0.11':
384 387 # I don't fully understand why we need this one, but depending on what
385 388 # directory the test suite is run from, if we don't give it, 0 tests
386 389 # get run. Specifically, if the test suite is run from the source dir
387 390 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
388 391 # even if the same call done in this directory works fine). It appears
389 392 # that if the requested package is in the current dir, nose bails early
390 393 # by default. Since it's otherwise harmless, leave it in by default
391 394 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
392 395 argv.append('--traverse-namespace')
393 396
394 397 # use our plugin for doctesting. It will remove the standard doctest plugin
395 398 # if it finds it enabled
396 399 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
397 400 # We need a global ipython running in this process
398 401 globalipapp.start_ipython()
399 402 # Now nose can run
400 403 TestProgram(argv=argv, addplugins=plugins)
401 404
402 405
403 406 def run_iptestall():
404 407 """Run the entire IPython test suite by calling nose and trial.
405 408
406 409 This function constructs :class:`IPTester` instances for all IPython
407 410 modules and package and then runs each of them. This causes the modules
408 411 and packages of IPython to be tested each in their own subprocess using
409 412 nose or twisted.trial appropriately.
410 413 """
411 414
412 415 runners = make_runners()
413 416
414 417 # Run the test runners in a temporary dir so we can nuke it when finished
415 418 # to clean up any junk files left over by accident. This also makes it
416 419 # robust against being run in non-writeable directories by mistake, as the
417 420 # temp dir will always be user-writeable.
418 421 curdir = os.getcwdu()
419 422 testdir = tempfile.gettempdir()
420 423 os.chdir(testdir)
421 424
422 425 # Run all test runners, tracking execution time
423 426 failed = []
424 427 t_start = time.time()
425 428 try:
426 429 for (name, runner) in runners:
427 430 print '*'*70
428 431 print 'IPython test group:',name
429 432 res = runner.run()
430 433 if res:
431 434 failed.append( (name, runner) )
432 435 finally:
433 436 os.chdir(curdir)
434 437 t_end = time.time()
435 438 t_tests = t_end - t_start
436 439 nrunners = len(runners)
437 440 nfail = len(failed)
438 441 # summarize results
439 442 print
440 443 print '*'*70
441 444 print 'Test suite completed for system with the following information:'
442 445 print report()
443 446 print 'Ran %s test groups in %.3fs' % (nrunners, t_tests)
444 447 print
445 448 print 'Status:'
446 449 if not failed:
447 450 print 'OK'
448 451 else:
449 452 # If anything went wrong, point out what command to rerun manually to
450 453 # see the actual errors and individual summary
451 454 print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners)
452 455 for name, failed_runner in failed:
453 456 print '-'*40
454 457 print 'Runner failed:',name
455 458 print 'You may wish to rerun this one individually, with:'
456 459 print ' '.join(failed_runner.call_args)
457 460 print
458 461 # Ensure that our exit code indicates failure
459 462 sys.exit(1)
460 463
461 464
462 465 def main():
463 466 for arg in sys.argv[1:]:
464 467 if arg.startswith('IPython'):
465 468 # This is in-process
466 469 run_iptest()
467 470 else:
468 471 # This starts subprocesses
469 472 run_iptestall()
470 473
471 474
472 475 if __name__ == '__main__':
473 476 main()
General Comments 0
You need to be logged in to leave comments. Login now