├── .gitignore ├── requirements.txt ├── .travis.yml ├── setup.py ├── pdb_ipython_nose.py ├── Example Usage.ipynb ├── README.rst ├── nose experiment.ipynb ├── test_ipython_nose.py └── ipython_nose.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .*.swp 3 | *.egg-info 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ipython>=0.13 2 | nose==1.2.1 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.6" 4 | - "2.7" 5 | install: pip install -r requirements.txt --use-mirrors 6 | script: nosetests test_ipython_nose.py 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | 4 | with open('README.rst') as f: 5 | long_description = f.read() 6 | 7 | 8 | setup( 9 | name='ipython_nose', 10 | version='0.1.0', 11 | author='Taavi Burns , Greg Ward ', 12 | py_modules=['ipython_nose'], 13 | url='https://github.com/taavi/ipython_nose', 14 | license='README.rst', 15 | description='IPython extension to run nosetests against the current kernel.', 16 | long_description=long_description, 17 | ) 18 | -------------------------------------------------------------------------------- /pdb_ipython_nose.py: -------------------------------------------------------------------------------- 1 | import types 2 | 3 | import ipython_nose 4 | 5 | test_module = types.ModuleType('test_module') 6 | from nose.plugins.skip import SkipTest 7 | 8 | def test_foo(): 9 | assert True 10 | 11 | def test_bar(): 12 | assert False 13 | 14 | def test_baz(): 15 | raise Exception() 16 | 17 | def test_quux(): 18 | raise SkipTest() 19 | 20 | test_module.test_foo = test_foo 21 | test_module.test_bar = test_bar 22 | test_module.test_baz = test_baz 23 | test_module.test_quux = test_quux 24 | 25 | plugin = ipython_nose.nose('', test_module) 26 | print plugin._repr_html_() 27 | -------------------------------------------------------------------------------- /Example Usage.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "Example Usage" 4 | }, 5 | "nbformat": 3, 6 | "nbformat_minor": 0, 7 | "worksheets": [ 8 | { 9 | "cells": [ 10 | { 11 | "cell_type": "code", 12 | "collapsed": false, 13 | "input": [ 14 | "%load_ext ipython_nose\n", 15 | "# %reload_ext ipython_nose" 16 | ], 17 | "language": "python", 18 | "metadata": {}, 19 | "outputs": [] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "collapsed": false, 24 | "input": [ 25 | "from nose import SkipTest\n", 26 | "\n", 27 | "def test_foo():\n", 28 | " assert True\n", 29 | " \n", 30 | "def test_bar():\n", 31 | " assert False\n", 32 | " \n", 33 | "def test_baz():\n", 34 | " raise SkipTest(\"Skipped\")\n", 35 | " \n", 36 | "import random\n", 37 | "import time\n", 38 | "def test_generate():\n", 39 | " for _ in range(random.randint(0, 10)):\n", 40 | " time.sleep(0.25)\n", 41 | " yield lambda x: None, _\n", 42 | " def fail(x):\n", 43 | " time.sleep(0.25)\n", 44 | " raise AssertionError(\"Failed\")\n", 45 | " for _ in range(random.randint(0, 10)):\n", 46 | " yield fail, _\n", 47 | " def skip(x):\n", 48 | " time.sleep(0.25)\n", 49 | " raise SkipTest(\"Skipped\")\n", 50 | " for _ in range(random.randint(0, 10)):\n", 51 | " yield skip, _" 52 | ], 53 | "language": "python", 54 | "metadata": {}, 55 | "outputs": [] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "collapsed": false, 60 | "input": [ 61 | "%nose" 62 | ], 63 | "language": "python", 64 | "metadata": {}, 65 | "outputs": [] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "collapsed": false, 70 | "input": [], 71 | "language": "python", 72 | "metadata": {}, 73 | "outputs": [] 74 | } 75 | ], 76 | "metadata": {} 77 | } 78 | ] 79 | } -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ipython_nose 2 | ------------ 3 | 4 | This little IPython extension gives you the ability to discover and 5 | run tests using Nose in an IPython Notebook. 6 | 7 | 8 | Installation 9 | ------------ 10 | 11 | * Make sure your IPython Notebook server can import ``ipython_nose.py`` (e.g. 12 | copy it to a directory in your ``PYTHONPATH``, or modify ``PYTHONPATH`` 13 | before starting IPython Notebook). It's also probably sufficient to have 14 | ``ipython_nose.py`` in the directory from which you run the notebook, e.g.:: 15 | 16 | $ ls 17 | ipython_nose.py 18 | $ ipython notebook 19 | 20 | * You can also install it in a virtualenv in developent mode:: 21 | 22 | $ cd ipython-nose 23 | $ pip install -e . 24 | 25 | 26 | Usage 27 | ----- 28 | 29 | * Add a cell containing:: 30 | 31 | %load_ext ipython_nose 32 | 33 | somewhere in your notebook. 34 | 35 | * Write tests that conform to Nose conventions, e.g.:: 36 | 37 | def test_arithmetic(): 38 | assert 1+1 == 2 39 | 40 | * Add a cell consisting of:: 41 | 42 | %nose 43 | 44 | to your notebook and run that cell. That will discover your 45 | ``test_*`` functions, run them, and report how many passed and 46 | how many failed, with stack traces for each failure. 47 | 48 | * Pass standard nose arguments to the magic:: 49 | 50 | %nose -v -x 51 | 52 | ``-v`` is handled specially, but other arguments are passed to nosetests as 53 | if they were passed at the command-line. 54 | 55 | 56 | Caveats 57 | ------- 58 | 59 | * Renaming tests leaves behind the old name: you might only see N 60 | test methods in your notebook, but Nose will discover and run N+1 61 | tests. Not sure how to fix this one. 62 | 63 | * Links between the stack traces and the code are only cell deep. For example, 64 | in a stack trace that looks like:: 65 | 66 | Traceback (most recent call last): 67 | File "/usr/lib/python2.7/dist-packages/nose/case.py", line 197, in runTest 68 | self.test(*self.arg) 69 | File "", line 2, in test_myfunc 70 | assert myfunc() == 42 71 | AssertionError 72 | 73 | the frame name ``ipython-input-10-a3ae96abafeb`` is a link to cell 10, but 74 | not specifically to line 2. 75 | 76 | 77 | TODO 78 | ---- 79 | 80 | * Have a cell magic to only run test-like things in the current cell, e.g.:: 81 | 82 | %%nose 83 | 84 | def test_just_this(): 85 | assert True 86 | 87 | 88 | Authors 89 | ------- 90 | 91 | * Taavi Burns 92 | * Greg Ward 93 | 94 | Thanks to Fernando Perez and Greg Wilson for tips, ideas, etc. 95 | 96 | Thanks to Catherine Devlin for publishing `ipython_doctester 97 | `_ so we could peek 98 | at its guts. 99 | 100 | 101 | Get the code 102 | ------------ 103 | 104 | :: 105 | 106 | git clone https://github.com/taavi/ipython_nose.git 107 | 108 | 109 | Copyright 110 | --------- 111 | 112 | Copyright (c) 2012, Taavi Burns, Greg Ward. 113 | 114 | All rights reserved. 115 | 116 | Redistribution and use in source and binary forms, with or without 117 | modification, are permitted provided that the following conditions are met: 118 | 119 | Redistributions of source code must retain the above copyright notice, this 120 | list of conditions and the following disclaimer. 121 | 122 | Redistributions in binary form must reproduce the above copyright notice, this 123 | list of conditions and the following disclaimer in the documentation and/or 124 | other materials provided with the distribution. 125 | 126 | Neither the name of the developers nor the names of contributors may 127 | be used to endorse or promote products derived from this software 128 | without specific prior written permission. 129 | 130 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 131 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 132 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 133 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 134 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 135 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 136 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 137 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 138 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 139 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 140 | -------------------------------------------------------------------------------- /nose experiment.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": { 3 | "name": "nose experiment" 4 | }, 5 | "nbformat": 3, 6 | "nbformat_minor": 0, 7 | "worksheets": [ 8 | { 9 | "cells": [ 10 | { 11 | "cell_type": "code", 12 | "collapsed": false, 13 | "input": [ 14 | "import sys, os\n", 15 | "import logging\n", 16 | "import unittest\n", 17 | "\n", 18 | "log = logging.getLogger()\n", 19 | "\n", 20 | "from nose import core, loader\n", 21 | "\n", 22 | "#logging.basicConfig(level=logging.DEBUG)\n", 23 | "\n", 24 | "from types import ModuleType" 25 | ], 26 | "language": "python", 27 | "metadata": {}, 28 | "outputs": [], 29 | "prompt_number": 1 30 | }, 31 | { 32 | "cell_type": "code", 33 | "collapsed": false, 34 | "input": [ 35 | "def adder(a, b):\n", 36 | " return a + b\n", 37 | "\n", 38 | "def test_nothing():\n", 39 | " assert 1+1 == 2\n", 40 | "\n", 41 | "def test_adder():\n", 42 | " assert adder(3, 4) == 7\n", 43 | " assert adder(-3, -5) == -8\n", 44 | " \n", 45 | "import random\n", 46 | "def test_random_generated_tests():\n", 47 | " for _ in range(random.randint(1, 100)):\n", 48 | " yield lambda: None\n", 49 | "\n", 50 | "flake = False\n", 51 | "def test_flake():\n", 52 | " global flake\n", 53 | " flake = not flake\n", 54 | " assert flake" 55 | ], 56 | "language": "python", 57 | "metadata": {}, 58 | "outputs": [], 59 | "prompt_number": 2 60 | }, 61 | { 62 | "cell_type": "code", 63 | "collapsed": false, 64 | "input": [ 65 | "class MyProgram(core.TestProgram):\n", 66 | " # XXX yuck: copy superclass runTests() so we can instantiate our own runner class;\n", 67 | " # can't do it early because we don't have access to nose's config object.\n", 68 | " def runTests(self):\n", 69 | " self.testRunner = MyRunner(self.config)\n", 70 | " # the rest is mostly duplicate code ;-(\n", 71 | " plug_runner = self.config.plugins.prepareTestRunner(self.testRunner)\n", 72 | " if plug_runner is not None:\n", 73 | " self.testRunner = plug_runner\n", 74 | " self.result = self.testRunner.run(self.test)\n", 75 | " self.success = self.result.wasSuccessful()\n", 76 | " return self.success\n", 77 | "\n", 78 | "class MyResult(unittest.TestResult):\n", 79 | " def make_bar(self, tests, failing):\n", 80 | " return '''
\n", 81 | "
 
\n", 82 | "
 
\n", 83 | "
''' % (\n", 84 | " failing * 10, (tests - failing) * 10)\n", 85 | "\n", 86 | " def make_table_of_tests(self, tests):\n", 87 | " table = ''\n", 88 | " for test in tests:\n", 89 | " table += '' % (\n", 90 | " str(test[0]), test[1])\n", 91 | " table += '
%s
'\n", 92 | " return table\n", 93 | " \n", 94 | " def _repr_html_(self):\n", 95 | " if self.errors or self.failures:\n", 96 | " not_successes = len(self.errors) + len(self.failures)\n", 97 | " return self.make_bar(self.testsRun, not_successes) + \\\n", 98 | " '''\n", 99 | "

Errors

%s\n", 100 | "

Failures

%s''' % (\n", 101 | " self.make_table_of_tests(self.errors),\n", 102 | " self.make_table_of_tests(self.failures))\n", 103 | " else:\n", 104 | " return self.make_bar(self.testsRun, 0) + '
%d/%d tests passed
' % (\n", 105 | " self.testsRun, self.testsRun)\n", 106 | "\n", 107 | "class MyRunner(object):\n", 108 | " def __init__(self, config):\n", 109 | " self.config = config\n", 110 | "\n", 111 | " def run(self, test):\n", 112 | " result = MyResult()\n", 113 | " if hasattr(result, 'startTestRun'): # python 2.7\n", 114 | " result.startTestRun()\n", 115 | " test(result)\n", 116 | " if hasattr(result, 'stopTestRun'):\n", 117 | " result.stopTestRun()\n", 118 | " self.config.plugins.finalize(result)\n", 119 | " self.result = result\n", 120 | " return result" 121 | ], 122 | "language": "python", 123 | "metadata": {}, 124 | "outputs": [], 125 | "prompt_number": 3 126 | }, 127 | { 128 | "cell_type": "code", 129 | "collapsed": false, 130 | "input": [ 131 | "test_module = ModuleType('test_module')\n", 132 | "test_module.__dict__.update(get_ipython().user_ns)\n", 133 | "\n", 134 | "ldr = loader.TestLoader()\n", 135 | "tests = ldr.loadTestsFromModule(test_module)\n", 136 | "#print(\"discovered: %r\" % list(tests._tests))\n", 137 | "\n", 138 | "tprog = MyProgram(argv=['dummy'], exit=False, suite=tests)\n", 139 | "tprog.result" 140 | ], 141 | "language": "python", 142 | "metadata": {}, 143 | "outputs": [ 144 | { 145 | "html": [ 146 | "
\n", 147 | "
 
\n", 148 | "
 
\n", 149 | "
62/62 tests passed
" 150 | ], 151 | "output_type": "pyout", 152 | "prompt_number": 4, 153 | "text": [ 154 | "<__main__.MyResult run=62 errors=0 failures=0>" 155 | ] 156 | } 157 | ], 158 | "prompt_number": 4 159 | } 160 | ], 161 | "metadata": {} 162 | } 163 | ] 164 | } -------------------------------------------------------------------------------- /test_ipython_nose.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from nose.tools import eq_ 4 | 5 | import ipython_nose 6 | 7 | 8 | def assert_in(subject, container): 9 | if subject not in container: 10 | raise AssertionError("'%s' not in '%s'" % (subject, container)) 11 | 12 | 13 | def assert_not_in(subject, container): 14 | if subject in container: 15 | raise AssertionError("'%s' contains '%s'" % (container, subject)) 16 | 17 | 18 | def get_raised_exception_tuple_with_message(message): 19 | try: 20 | raise Exception(message) 21 | except Exception as e: 22 | return sys.exc_info() 23 | 24 | 25 | class TestTemplate(object): 26 | def test_str(self): 27 | template = ipython_nose.Template('{var}') 28 | eq_('val') 147 | tracebacks = self.plugin._tracebacks( 148 | [ 149 | (FakeTest(), exception_tuple), 150 | ], 151 | self.plugin._tracebacks_template_html 152 | ) 153 | assert_in('<', tracebacks) 154 | 155 | def test_tracebacks_in_html_escapes_traceback(self): 156 | exception_tuple = get_raised_exception_tuple_with_message('>') 157 | tracebacks = self.plugin._tracebacks( 158 | [ 159 | (FakeTest(), exception_tuple), 160 | ], 161 | self.plugin._tracebacks_template_html 162 | 163 | ) 164 | assert_in('>', tracebacks) 165 | 166 | def test_tracebacks_in_text_does_not_escape_test_name(self): 167 | exception_tuple = get_raised_exception_tuple_with_message('>') 168 | tracebacks = self.plugin._tracebacks( 169 | [ 170 | (FakeTest(), exception_tuple), 171 | ], 172 | self.plugin._tracebacks_template_text 173 | ) 174 | assert_in('>', tracebacks) 175 | 176 | def test_tracebacks_in_text_does_not_escape_traceback(self): 177 | exception_tuple = get_raised_exception_tuple_with_message('>') 178 | tracebacks = self.plugin._tracebacks( 179 | [ 180 | (FakeTest(), exception_tuple), 181 | ], 182 | self.plugin._tracebacks_template_text 183 | 184 | ) 185 | assert_in('>', tracebacks) 186 | 187 | def test_linkify_html_traceback(self): 188 | frame_number = '1' 189 | frame_name = 'ipython-input-' + frame_number + '-0123456789ab' 190 | linkified = self.plugin.linkify_html_traceback( 191 | '<' + frame_name + '>') 192 | expected_link = ( 193 | '<' + 196 | frame_name + 197 | '') 198 | assert expected_link in linkified, "\n%s\nnot in\n%s" % ( 199 | expected_link, linkified) 200 | expected_selector = ( 201 | '$("div.prompt.input_prompt:contains([' + 202 | frame_number + 203 | '])")') 204 | assert expected_selector in linkified, "\n%s\nnot in\n%s" % ( 205 | expected_selector, linkified) 206 | -------------------------------------------------------------------------------- /ipython_nose.py: -------------------------------------------------------------------------------- 1 | import cgi 2 | import os 3 | import traceback 4 | import re 5 | import shlex 6 | import string 7 | import sys 8 | import types 9 | import uuid 10 | 11 | from nose import core as nose_core 12 | from nose import loader as nose_loader 13 | from nose.config import Config, all_config_files 14 | from nose.plugins.base import Plugin 15 | from nose.plugins.skip import SkipTest 16 | from nose.plugins.manager import DefaultPluginManager 17 | from IPython.core import displaypub, magic 18 | 19 | 20 | class Template(string.Formatter): 21 | def __init__(self, template): 22 | self._template = template 23 | 24 | def format(self, **context): 25 | return self.vformat(self._template, (), context) 26 | 27 | def convert_field(self, value, conversion): 28 | if conversion == 'e': 29 | return cgi.escape(value) 30 | else: 31 | return super(Template, self).convert_field(value, conversion) 32 | 33 | 34 | class DummyUnittestStream: 35 | def write(self, *arg): 36 | pass 37 | 38 | def writeln(self, *arg): 39 | pass 40 | 41 | def flush(self, *arg): 42 | pass 43 | 44 | 45 | class NotebookLiveOutput(object): 46 | def __init__(self): 47 | self.output_id = 'ipython_nose_%s' % uuid.uuid4().hex 48 | displaypub.publish_display_data( 49 | u'IPython.core.displaypub.publish_html', 50 | {'text/html':'
' % self.output_id}) 51 | displaypub.publish_display_data( 52 | u'IPython.core.displaypub.publish_javascript', 53 | {'application/javascript': 54 | 'document.%s = $("#%s");' % (self.output_id, self.output_id)}) 55 | 56 | def finalize(self): 57 | displaypub.publish_display_data( 58 | u'IPython.core.displaypub.publish_javascript', 59 | {'application/javascript': 60 | 'delete document.%s;' % self.output_id}) 61 | 62 | 63 | def write_chars(self, chars): 64 | displaypub.publish_display_data( 65 | u'IPython.core.displaypub.publish_javascript', 66 | {'application/javascript': 67 | 'document.%s.append($("%s"));' % ( 68 | self.output_id, cgi.escape(chars))}) 69 | 70 | def write_line(self, line): 71 | displaypub.publish_display_data( 72 | u'IPython.core.displaypub.publish_javascript', 73 | {'application/javascript': 74 | 'document.%s.append($("
%s
"));' % ( 75 | self.output_id, cgi.escape(line))}) 76 | 77 | 78 | 79 | class ConsoleLiveOutput(object): 80 | def __init__(self, stream_obj): 81 | self.stream_obj = stream_obj 82 | 83 | def finalize(self): 84 | self.stream_obj.stream.write('\n') 85 | 86 | def write_chars(self, chars): 87 | self.stream_obj.stream.write(chars) 88 | 89 | def write_line(self, line): 90 | self.stream_obj.stream.write(line + '\n') 91 | 92 | 93 | def html_escape(s): 94 | return cgi.escape(str(s)) 95 | 96 | 97 | class IPythonDisplay(Plugin): 98 | """Do something nice in IPython.""" 99 | 100 | name = 'ipython-html' 101 | enabled = True 102 | score = 2 103 | 104 | def __init__(self, verbose=False): 105 | super(IPythonDisplay, self).__init__() 106 | self.verbose = verbose 107 | self.html = [] 108 | self.num_tests = 0 109 | self.failures = [] 110 | self.skipped = 0 111 | 112 | _nose_css = '''\ 113 | 190 | ''' 191 | 192 | _show_hide_js = ''' 193 | 209 | ''' 210 | 211 | _summary_template_html = Template(''' 212 |
213 |
214 |   215 |
216 | 219 |
220 |   221 |
222 | {text!e} 223 |
224 | ''') 225 | 226 | _summary_template_text = Template('''{text}\n''') 227 | 228 | def _summary(self, numtests, numfailed, numskipped, template): 229 | text = "%d/%d tests passed" % (numtests - numfailed, numtests) 230 | if numfailed > 0: 231 | text += "; %d failed" % numfailed 232 | if numskipped > 0: 233 | text += "; %d skipped" % numskipped 234 | 235 | failpercent = int(float(numfailed) / numtests * 100) 236 | if numfailed > 0 and failpercent < 5: 237 | # Ensure the red bar is visible 238 | failpercent = 5 239 | 240 | skippercent = int(float(numskipped) / numtests * 100) 241 | if numskipped > 0 and skippercent < 5: 242 | # Ditto for the yellow bar 243 | skippercent = 5 244 | 245 | passpercent = 100 - failpercent - skippercent 246 | 247 | return template.format( 248 | text=text, failpercent=failpercent, skippercent=skippercent, 249 | passpercent=passpercent) 250 | 251 | _tracebacks_template_html = Template(''' 252 |
253 |
254 | failed: {name!e} 255 | [toggle traceback] 256 |
257 |
{formatted_traceback!e}
258 |
259 | ''') 260 | 261 | _tracebacks_template_text = Template( 262 | '''========\n{name}\n========\n{formatted_traceback}\n''') 263 | 264 | def _tracebacks(self, failures, template): 265 | output = [] 266 | for test, exc in failures: 267 | name = test.shortDescription() or str(test) 268 | formatted_traceback = ''.join(traceback.format_exception(*exc)) 269 | output.append(template.format( 270 | name=name, formatted_traceback=formatted_traceback 271 | )) 272 | return ''.join(output) 273 | 274 | def addSuccess(self, test): 275 | if self.verbose: 276 | self.live_output.write_line(str(test) + " ... pass") 277 | else: 278 | self.live_output.write_chars('.') 279 | 280 | def addError(self, test, err): 281 | if issubclass(err[0], SkipTest): 282 | return self.addSkip(test) 283 | if self.verbose: 284 | self.live_output.write_line(str(test) + " ... error") 285 | else: 286 | self.live_output.write_chars('E') 287 | self.failures.append((test, err)) 288 | 289 | def addFailure(self, test, err): 290 | if self.verbose: 291 | self.live_output.write_line(str(test) + " ... fail") 292 | else: 293 | self.live_output.write_chars('F') 294 | self.failures.append((test, err)) 295 | 296 | # Deprecated in newer versions of nose; skipped tests are handled in 297 | # addError in newer versions 298 | def addSkip(self, test): 299 | if self.verbose: 300 | self.live_output.write_line(str(test) + " ... SKIP") 301 | else: 302 | self.live_output.write_chars('S') 303 | self.skipped += 1 304 | 305 | def begin(self): 306 | # This feels really hacky 307 | try: # >= ipython 1.0 308 | from IPython.kernel.zmq.displayhook import ZMQShellDisplayHook 309 | except ImportError: 310 | from IPython.zmq.displayhook import ZMQShellDisplayHook 311 | if isinstance(sys.displayhook, ZMQShellDisplayHook): 312 | self.live_output = NotebookLiveOutput() 313 | else: 314 | self.live_output = ConsoleLiveOutput(self) 315 | 316 | def finalize(self, result): 317 | self.result = result 318 | self.live_output.finalize() 319 | 320 | def setOutputStream(self, stream): 321 | # grab for own use 322 | self.stream = stream 323 | return DummyUnittestStream() 324 | 325 | def startContext(self, ctx): 326 | pass 327 | 328 | def stopContext(self, ctx): 329 | pass 330 | 331 | def startTest(self, test): 332 | self.num_tests += 1 333 | 334 | def stopTest(self, test): 335 | pass 336 | 337 | @staticmethod 338 | def make_link(matches): 339 | target = matches.group(0) 340 | input_id = matches.group(1) 341 | link = '{target}'.format(target=target) 342 | make_anchor_js = ''''''.format(input_id=input_id, target=target) 346 | return link + make_anchor_js 347 | 348 | def linkify_html_traceback(self, html): 349 | return re.sub( 350 | r'ipython-input-(\d+)-[0-9a-f]{12}', 351 | self.make_link, 352 | html) 353 | 354 | def _repr_html_(self): 355 | if self.num_tests <= 0: 356 | return 'No tests found.' 357 | 358 | output = [self._nose_css, self._show_hide_js] 359 | 360 | output.append(self._summary( 361 | self.num_tests, len(self.failures), self.skipped, 362 | self._summary_template_html)) 363 | output.append(self.linkify_html_traceback(self._tracebacks( 364 | self.failures, self._tracebacks_template_html))) 365 | return ''.join(output) 366 | 367 | def _repr_pretty_(self, p, cycle): 368 | if self.num_tests <= 0: 369 | p.text('No tests found.') 370 | return 371 | p.text(self._summary( 372 | self.num_tests, len(self.failures), self.skipped, 373 | self._summary_template_text)) 374 | p.text(self._tracebacks(self.failures, self._tracebacks_template_text)) 375 | 376 | 377 | def get_ipython_user_ns_as_a_module(): 378 | test_module = types.ModuleType('test_module') 379 | test_module.__dict__.update(get_ipython().user_ns) 380 | return test_module 381 | 382 | 383 | def makeNoseConfig(env): 384 | """Load a Config, pre-filled with user config files if any are 385 | found. 386 | """ 387 | cfg_files = all_config_files() 388 | manager = DefaultPluginManager() 389 | return Config(env=env, files=cfg_files, plugins=manager) 390 | 391 | 392 | def nose(line, test_module=get_ipython_user_ns_as_a_module): 393 | if callable(test_module): 394 | test_module = test_module() 395 | config = makeNoseConfig(os.environ) 396 | loader = nose_loader.TestLoader(config=config) 397 | tests = loader.loadTestsFromModule(test_module) 398 | extra_args = shlex.split(str(line)) 399 | argv = ['ipython-nose', '--with-ipython-html', '--no-skip'] + extra_args 400 | verbose = '-v' in extra_args 401 | plug = IPythonDisplay(verbose=verbose) 402 | 403 | nose_core.TestProgram( 404 | argv=argv, suite=tests, addplugins=[plug], exit=False, config=config) 405 | 406 | return plug 407 | 408 | 409 | def load_ipython_extension(ipython): 410 | magic.register_line_magic(nose) 411 | --------------------------------------------------------------------------------