Source code for interactive.shell
"""
An extended shell for test selection
"""
import keyword
import re
from IPython.terminal.embed import InteractiveShellEmbed
from IPython.core.magic import (Magics, magics_class, line_magic)
from IPython.core.history import HistoryManager
from IPython.terminal.prompts import Prompts, Token
class TestCounterPrompt(Prompts):
def in_prompt_tokens(self, cli=None):
"""Render a simple prompt which reports the number of currently
selected tests.
"""
return [
(Token.PromptNum, '{}'.format(
len(self.shell.user_ns['_selection']))),
(Token.Prompt, ' selected >>> '),
]
[docs]class PytestShellEmbed(InteractiveShellEmbed):
"""Custom ip shell with a slightly altered exit message
"""
prompts_class = TestCounterPrompt
# cause if you don't use it shame on you
editing_mode = 'vi'
[docs] def init_history(self):
"""Sets up the command history, and starts regular autosaves.
.. note::
A separate history db is allocated for this plugin separate
from regular shell sessions such that only relevant commands
are retained.
"""
self.history_manager = HistoryManager(
shell=self, parent=self, hist_file=self.pytest_hist_file)
self.configurables.append(self.history_manager)
[docs] def exit(self):
"""Handle interactive exit.
This method calls the ``ask_exit`` callback and if applicable prompts
the user to verify the current test selection
"""
if getattr(self, 'selection', None):
print(" \n".join(self.selection.keys()))
msg = "\nYou have selected the above {} test(s) to be run."\
"\nWould you like to run pytest now? ([y]/n)?"\
.format(len(self.selection))
else:
msg = 'Do you really want to exit ([y]/n)?'
if self.ask_yes_no(msg, 'y'):
# sets self.keep_running to False
self.ask_exit()
[docs]@magics_class
class SelectionMagics(Magics):
"""Custom magics for performing multiple test selections
within a single session
"""
# XXX do we actually need this or can we do `user_ns` lookups?
[docs] def ns_eval(self, line):
'''Evalutate line in the embedded ns and return result
'''
ns = self.shell.user_ns
return eval(line, ns)
@property
def tt(self):
return self.ns_eval('_tree')
@property
def selection(self):
return self.tt._selection
@property
def tr(self):
return self.tt._tr
def err(self, msg="No tests selected"):
self.tt.err(msg)
[docs] @line_magic
def add(self, line):
'''Add tests from a test set to the current selection.
Usage:
add tt : add all tests in the current tree
add tt[4] : add 5th test in the current tree
add tt.tests[1:10] : add tests 1-9 found under the 'tests' module
'''
if line:
ts = self.ns_eval(line)
if ts:
self.selection.addtests(ts)
else:
raise TypeError("'{}' is not a test set".format(ts))
else:
print("No test set provided?")
[docs] @line_magic
def remove(self, line, delim=','):
"""Remove tests from the current selection using a slice syntax
using a ',' delimiter instead of ':'.
Usage:
remove : remove all tests from the current selection
remove -1 : remove the last item from the selection
remove 1, : remove all but the first item (same as [1:])
remove ,,-3 : remove every third item (same as [::-3])
"""
selection = self.selection
if not self.selection:
self.err()
return
if not line:
selection.clear()
return
# parse out slice
if delim in line:
slc = slice(*map(lambda x: int(x.strip()) if x.strip() else None,
line.split(delim)))
for item in selection[slc]:
selection.remove(item)
else: # just an index
try:
selection.remove(selection[int(line)])
except ValueError:
self.err("'{}' is not and index or slice?".format(line))
[docs] @line_magic
def show(self, test_set):
'''Show all currently selected test by pretty printing
to the console.
Usage:
show: print currently selected tests
'''
items = self.selection.values()
if items:
self.tt._tprint(items)
else:
self.err()
[docs] @line_magic
def cache(self, line, ident='pytest/interactive'):
"""Store a set of tests in the pytest cache for retrieval in another
session.
Usage:
cache: show a summary of names previously stored in the cache.
cache del <name>: deletes the named entry from the cache.
cache add <name> <target>: stores the named tests as target name.
"""
cachedict = self.tt.get_cache_dict()
if line:
tokens = line.split()
subcmd, name = tokens[0], tokens[1]
if subcmd == 'del': # delete an entry
name = tokens[1]
self.tt.set_cache_items(name, None) # delete
self.shell.user_ns.pop(name)
self.tr.write(
"Deleted cache entry for '{}'\n".format(name))
return
elif subcmd == 'add': # create a new entry
target = tokens[2]
if not re.match("[_A-Za-z][_a-zA-Z0-9]*$", target) \
and not keyword.iskeyword(name):
self.tt.err("'{}' is not a valid identifier"
.format(target))
return
testset = self.ns_eval(name)
self.tt.set_cache_items(target, testset)
# update the local shell's ns
if testset:
self.shell.user_ns[target] = testset
self.tr.write(
"Created cache entry for '{}'\n".format(name))
return
self.tt.err("'{}' is invalid. See %cache? for usage.".format(line))
else:
tr = self.tr
tr.write("\nSummary:\n", green=True)
for name, testnames in cachedict.items():
tr.write('{} -> {} items\n'.format(name, len(testnames)))