##// END OF EJS Templates
handle lecture test moving to lib
Min RK -
Show More
@@ -1,131 +1,122 b''
1 """Test lexers module"""
1 """Test lexers module"""
2 #-----------------------------------------------------------------------------
2
3 # Copyright (C) 2014 The IPython Development Team
3 # Copyright (c) IPython Development Team.
4 #
4 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the BSD License. The full license is in
5
6 # the file COPYING, distributed as part of this software.
6 from unittest import TestCase
7 #-----------------------------------------------------------------------------
8
9 #-----------------------------------------------------------------------------
10 # Imports
11 #-----------------------------------------------------------------------------
12 from pygments.token import Token
7 from pygments.token import Token
13
8
14 from jupyter_nbconvert.tests.base import TestsBase
15 from .. import lexers
9 from .. import lexers
16
10
17
11
18 #-----------------------------------------------------------------------------
12 class TestLexers(TestCase):
19 # Classes and functions
20 #-----------------------------------------------------------------------------
21 class TestLexers(TestsBase):
22 """Collection of lexers tests"""
13 """Collection of lexers tests"""
23 def setUp(self):
14 def setUp(self):
24 self.lexer = lexers.IPythonLexer()
15 self.lexer = lexers.IPythonLexer()
25
16
26 def testIPythonLexer(self):
17 def testIPythonLexer(self):
27 fragment = '!echo $HOME\n'
18 fragment = '!echo $HOME\n'
28 tokens = [
19 tokens = [
29 (Token.Operator, '!'),
20 (Token.Operator, '!'),
30 (Token.Name.Builtin, 'echo'),
21 (Token.Name.Builtin, 'echo'),
31 (Token.Text, ' '),
22 (Token.Text, ' '),
32 (Token.Name.Variable, '$HOME'),
23 (Token.Name.Variable, '$HOME'),
33 (Token.Text, '\n'),
24 (Token.Text, '\n'),
34 ]
25 ]
35 self.assertEqual(tokens, list(self.lexer.get_tokens(fragment)))
26 self.assertEqual(tokens, list(self.lexer.get_tokens(fragment)))
36
27
37 fragment_2 = '!' + fragment
28 fragment_2 = '!' + fragment
38 tokens_2 = [
29 tokens_2 = [
39 (Token.Operator, '!!'),
30 (Token.Operator, '!!'),
40 ] + tokens[1:]
31 ] + tokens[1:]
41 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
32 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
42
33
43 fragment_2 = '\t %%!\n' + fragment[1:]
34 fragment_2 = '\t %%!\n' + fragment[1:]
44 tokens_2 = [
35 tokens_2 = [
45 (Token.Text, '\t '),
36 (Token.Text, '\t '),
46 (Token.Operator, '%%!'),
37 (Token.Operator, '%%!'),
47 (Token.Text, '\n'),
38 (Token.Text, '\n'),
48 ] + tokens[1:]
39 ] + tokens[1:]
49 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
40 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
50
41
51 fragment_2 = 'x = ' + fragment
42 fragment_2 = 'x = ' + fragment
52 tokens_2 = [
43 tokens_2 = [
53 (Token.Name, 'x'),
44 (Token.Name, 'x'),
54 (Token.Text, ' '),
45 (Token.Text, ' '),
55 (Token.Operator, '='),
46 (Token.Operator, '='),
56 (Token.Text, ' '),
47 (Token.Text, ' '),
57 ] + tokens
48 ] + tokens
58 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
49 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
59
50
60 fragment_2 = 'x, = ' + fragment
51 fragment_2 = 'x, = ' + fragment
61 tokens_2 = [
52 tokens_2 = [
62 (Token.Name, 'x'),
53 (Token.Name, 'x'),
63 (Token.Punctuation, ','),
54 (Token.Punctuation, ','),
64 (Token.Text, ' '),
55 (Token.Text, ' '),
65 (Token.Operator, '='),
56 (Token.Operator, '='),
66 (Token.Text, ' '),
57 (Token.Text, ' '),
67 ] + tokens
58 ] + tokens
68 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
59 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
69
60
70 fragment_2 = 'x, = %sx ' + fragment[1:]
61 fragment_2 = 'x, = %sx ' + fragment[1:]
71 tokens_2 = [
62 tokens_2 = [
72 (Token.Name, 'x'),
63 (Token.Name, 'x'),
73 (Token.Punctuation, ','),
64 (Token.Punctuation, ','),
74 (Token.Text, ' '),
65 (Token.Text, ' '),
75 (Token.Operator, '='),
66 (Token.Operator, '='),
76 (Token.Text, ' '),
67 (Token.Text, ' '),
77 (Token.Operator, '%'),
68 (Token.Operator, '%'),
78 (Token.Keyword, 'sx'),
69 (Token.Keyword, 'sx'),
79 (Token.Text, ' '),
70 (Token.Text, ' '),
80 ] + tokens[1:]
71 ] + tokens[1:]
81 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
72 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
82
73
83 fragment_2 = 'f = %R function () {}\n'
74 fragment_2 = 'f = %R function () {}\n'
84 tokens_2 = [
75 tokens_2 = [
85 (Token.Name, 'f'),
76 (Token.Name, 'f'),
86 (Token.Text, ' '),
77 (Token.Text, ' '),
87 (Token.Operator, '='),
78 (Token.Operator, '='),
88 (Token.Text, ' '),
79 (Token.Text, ' '),
89 (Token.Operator, '%'),
80 (Token.Operator, '%'),
90 (Token.Keyword, 'R'),
81 (Token.Keyword, 'R'),
91 (Token.Text, ' function () {}\n'),
82 (Token.Text, ' function () {}\n'),
92 ]
83 ]
93 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
84 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
94
85
95 fragment_2 = '\t%%xyz\n$foo\n'
86 fragment_2 = '\t%%xyz\n$foo\n'
96 tokens_2 = [
87 tokens_2 = [
97 (Token.Text, '\t'),
88 (Token.Text, '\t'),
98 (Token.Operator, '%%'),
89 (Token.Operator, '%%'),
99 (Token.Keyword, 'xyz'),
90 (Token.Keyword, 'xyz'),
100 (Token.Text, '\n$foo\n'),
91 (Token.Text, '\n$foo\n'),
101 ]
92 ]
102 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
93 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
103
94
104 fragment_2 = '%system?\n'
95 fragment_2 = '%system?\n'
105 tokens_2 = [
96 tokens_2 = [
106 (Token.Operator, '%'),
97 (Token.Operator, '%'),
107 (Token.Keyword, 'system'),
98 (Token.Keyword, 'system'),
108 (Token.Operator, '?'),
99 (Token.Operator, '?'),
109 (Token.Text, '\n'),
100 (Token.Text, '\n'),
110 ]
101 ]
111 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
102 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
112
103
113 fragment_2 = 'x != y\n'
104 fragment_2 = 'x != y\n'
114 tokens_2 = [
105 tokens_2 = [
115 (Token.Name, 'x'),
106 (Token.Name, 'x'),
116 (Token.Text, ' '),
107 (Token.Text, ' '),
117 (Token.Operator, '!='),
108 (Token.Operator, '!='),
118 (Token.Text, ' '),
109 (Token.Text, ' '),
119 (Token.Name, 'y'),
110 (Token.Name, 'y'),
120 (Token.Text, '\n'),
111 (Token.Text, '\n'),
121 ]
112 ]
122 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
113 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
123
114
124 fragment_2 = ' ?math.sin\n'
115 fragment_2 = ' ?math.sin\n'
125 tokens_2 = [
116 tokens_2 = [
126 (Token.Text, ' '),
117 (Token.Text, ' '),
127 (Token.Operator, '?'),
118 (Token.Operator, '?'),
128 (Token.Text, 'math.sin'),
119 (Token.Text, 'math.sin'),
129 (Token.Text, '\n'),
120 (Token.Text, '\n'),
130 ]
121 ]
131 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
122 self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
@@ -1,490 +1,492 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """IPython Test Suite Runner.
2 """IPython Test Suite Runner.
3
3
4 This module provides a main entry point to a user script to test IPython
4 This module provides a main entry point to a user script to test IPython
5 itself from the command line. There are two ways of running this script:
5 itself from the command line. There are two ways of running this script:
6
6
7 1. With the syntax `iptest all`. This runs our entire test suite by
7 1. With the syntax `iptest all`. This runs our entire test suite by
8 calling this script (with different arguments) recursively. This
8 calling this script (with different arguments) recursively. This
9 causes modules and package to be tested in different processes, using nose
9 causes modules and package to be tested in different processes, using nose
10 or trial where appropriate.
10 or trial where appropriate.
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 the script simply calls nose, but with special command line flags and
12 the script simply calls nose, but with special command line flags and
13 plugins loaded.
13 plugins loaded.
14
14
15 """
15 """
16
16
17 # Copyright (c) IPython Development Team.
17 # Copyright (c) IPython Development Team.
18 # Distributed under the terms of the Modified BSD License.
18 # Distributed under the terms of the Modified BSD License.
19
19
20 from __future__ import print_function
20 from __future__ import print_function
21
21
22 import glob
22 import glob
23 from io import BytesIO
23 from io import BytesIO
24 import os
24 import os
25 import os.path as path
25 import os.path as path
26 import sys
26 import sys
27 from threading import Thread, Lock, Event
27 from threading import Thread, Lock, Event
28 import warnings
28 import warnings
29
29
30 import nose.plugins.builtin
30 import nose.plugins.builtin
31 from nose.plugins.xunit import Xunit
31 from nose.plugins.xunit import Xunit
32 from nose import SkipTest
32 from nose import SkipTest
33 from nose.core import TestProgram
33 from nose.core import TestProgram
34 from nose.plugins import Plugin
34 from nose.plugins import Plugin
35 from nose.util import safe_str
35 from nose.util import safe_str
36
36
37 from IPython.utils.process import is_cmd_found
37 from IPython.utils.process import is_cmd_found
38 from IPython.utils.py3compat import bytes_to_str
38 from IPython.utils.py3compat import bytes_to_str
39 from IPython.utils.importstring import import_item
39 from IPython.utils.importstring import import_item
40 from IPython.testing.plugin.ipdoctest import IPythonDoctest
40 from IPython.testing.plugin.ipdoctest import IPythonDoctest
41 from IPython.external.decorators import KnownFailure, knownfailureif
41 from IPython.external.decorators import KnownFailure, knownfailureif
42
42
43 pjoin = path.join
43 pjoin = path.join
44
44
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46 # Warnings control
46 # Warnings control
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48
48
49 # Twisted generates annoying warnings with Python 2.6, as will do other code
49 # Twisted generates annoying warnings with Python 2.6, as will do other code
50 # that imports 'sets' as of today
50 # that imports 'sets' as of today
51 warnings.filterwarnings('ignore', 'the sets module is deprecated',
51 warnings.filterwarnings('ignore', 'the sets module is deprecated',
52 DeprecationWarning )
52 DeprecationWarning )
53
53
54 # This one also comes from Twisted
54 # This one also comes from Twisted
55 warnings.filterwarnings('ignore', 'the sha module is deprecated',
55 warnings.filterwarnings('ignore', 'the sha module is deprecated',
56 DeprecationWarning)
56 DeprecationWarning)
57
57
58 # Wx on Fedora11 spits these out
58 # Wx on Fedora11 spits these out
59 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
59 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
60 UserWarning)
60 UserWarning)
61
61
62 # ------------------------------------------------------------------------------
62 # ------------------------------------------------------------------------------
63 # Monkeypatch Xunit to count known failures as skipped.
63 # Monkeypatch Xunit to count known failures as skipped.
64 # ------------------------------------------------------------------------------
64 # ------------------------------------------------------------------------------
65 def monkeypatch_xunit():
65 def monkeypatch_xunit():
66 try:
66 try:
67 knownfailureif(True)(lambda: None)()
67 knownfailureif(True)(lambda: None)()
68 except Exception as e:
68 except Exception as e:
69 KnownFailureTest = type(e)
69 KnownFailureTest = type(e)
70
70
71 def addError(self, test, err, capt=None):
71 def addError(self, test, err, capt=None):
72 if issubclass(err[0], KnownFailureTest):
72 if issubclass(err[0], KnownFailureTest):
73 err = (SkipTest,) + err[1:]
73 err = (SkipTest,) + err[1:]
74 return self.orig_addError(test, err, capt)
74 return self.orig_addError(test, err, capt)
75
75
76 Xunit.orig_addError = Xunit.addError
76 Xunit.orig_addError = Xunit.addError
77 Xunit.addError = addError
77 Xunit.addError = addError
78
78
79 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
80 # Check which dependencies are installed and greater than minimum version.
80 # Check which dependencies are installed and greater than minimum version.
81 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
82 def extract_version(mod):
82 def extract_version(mod):
83 return mod.__version__
83 return mod.__version__
84
84
85 def test_for(item, min_version=None, callback=extract_version):
85 def test_for(item, min_version=None, callback=extract_version):
86 """Test to see if item is importable, and optionally check against a minimum
86 """Test to see if item is importable, and optionally check against a minimum
87 version.
87 version.
88
88
89 If min_version is given, the default behavior is to check against the
89 If min_version is given, the default behavior is to check against the
90 `__version__` attribute of the item, but specifying `callback` allows you to
90 `__version__` attribute of the item, but specifying `callback` allows you to
91 extract the value you are interested in. e.g::
91 extract the value you are interested in. e.g::
92
92
93 In [1]: import sys
93 In [1]: import sys
94
94
95 In [2]: from IPython.testing.iptest import test_for
95 In [2]: from IPython.testing.iptest import test_for
96
96
97 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
97 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
98 Out[3]: True
98 Out[3]: True
99
99
100 """
100 """
101 try:
101 try:
102 check = import_item(item)
102 check = import_item(item)
103 except (ImportError, RuntimeError):
103 except (ImportError, RuntimeError):
104 # GTK reports Runtime error if it can't be initialized even if it's
104 # GTK reports Runtime error if it can't be initialized even if it's
105 # importable.
105 # importable.
106 return False
106 return False
107 else:
107 else:
108 if min_version:
108 if min_version:
109 if callback:
109 if callback:
110 # extra processing step to get version to compare
110 # extra processing step to get version to compare
111 check = callback(check)
111 check = callback(check)
112
112
113 return check >= min_version
113 return check >= min_version
114 else:
114 else:
115 return True
115 return True
116
116
117 # Global dict where we can store information on what we have and what we don't
117 # Global dict where we can store information on what we have and what we don't
118 # have available at test run time
118 # have available at test run time
119 have = {}
119 have = {}
120
120
121 have['curses'] = test_for('_curses')
121 have['curses'] = test_for('_curses')
122 have['matplotlib'] = test_for('matplotlib')
122 have['matplotlib'] = test_for('matplotlib')
123 have['numpy'] = test_for('numpy')
123 have['numpy'] = test_for('numpy')
124 have['pexpect'] = test_for('pexpect')
124 have['pexpect'] = test_for('pexpect')
125 have['pymongo'] = test_for('pymongo')
125 have['pymongo'] = test_for('pymongo')
126 have['pygments'] = test_for('pygments')
126 have['pygments'] = test_for('pygments')
127 have['sqlite3'] = test_for('sqlite3')
127 have['sqlite3'] = test_for('sqlite3')
128 have['tornado'] = test_for('tornado.version_info', (4,0), callback=None)
128 have['tornado'] = test_for('tornado.version_info', (4,0), callback=None)
129 have['jinja2'] = test_for('jinja2')
129 have['jinja2'] = test_for('jinja2')
130 have['mistune'] = test_for('mistune')
130 have['mistune'] = test_for('mistune')
131 have['requests'] = test_for('requests')
131 have['requests'] = test_for('requests')
132 have['sphinx'] = test_for('sphinx')
132 have['sphinx'] = test_for('sphinx')
133 have['jsonschema'] = test_for('jsonschema')
133 have['jsonschema'] = test_for('jsonschema')
134 have['terminado'] = test_for('terminado')
134 have['terminado'] = test_for('terminado')
135 have['casperjs'] = is_cmd_found('casperjs')
135 have['casperjs'] = is_cmd_found('casperjs')
136 have['phantomjs'] = is_cmd_found('phantomjs')
136 have['phantomjs'] = is_cmd_found('phantomjs')
137 have['slimerjs'] = is_cmd_found('slimerjs')
137 have['slimerjs'] = is_cmd_found('slimerjs')
138
138
139 min_zmq = (13,)
139 min_zmq = (13,)
140
140
141 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
141 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
142
142
143 #-----------------------------------------------------------------------------
143 #-----------------------------------------------------------------------------
144 # Test suite definitions
144 # Test suite definitions
145 #-----------------------------------------------------------------------------
145 #-----------------------------------------------------------------------------
146
146
147 test_group_names = ['core',
147 test_group_names = ['core',
148 'extensions', 'lib', 'terminal', 'testing', 'utils',
148 'extensions', 'lib', 'terminal', 'testing', 'utils',
149 'html', 'nbconvert'
149 'html', 'nbconvert'
150 ]
150 ]
151
151
152 class TestSection(object):
152 class TestSection(object):
153 def __init__(self, name, includes):
153 def __init__(self, name, includes):
154 self.name = name
154 self.name = name
155 self.includes = includes
155 self.includes = includes
156 self.excludes = []
156 self.excludes = []
157 self.dependencies = []
157 self.dependencies = []
158 self.enabled = True
158 self.enabled = True
159
159
160 def exclude(self, module):
160 def exclude(self, module):
161 if not module.startswith('IPython'):
161 if not module.startswith('IPython'):
162 module = self.includes[0] + "." + module
162 module = self.includes[0] + "." + module
163 self.excludes.append(module.replace('.', os.sep))
163 self.excludes.append(module.replace('.', os.sep))
164
164
165 def requires(self, *packages):
165 def requires(self, *packages):
166 self.dependencies.extend(packages)
166 self.dependencies.extend(packages)
167
167
168 @property
168 @property
169 def will_run(self):
169 def will_run(self):
170 return self.enabled and all(have[p] for p in self.dependencies)
170 return self.enabled and all(have[p] for p in self.dependencies)
171
171
172 shims = {
172 shims = {
173 'html': 'jupyter_notebook',
173 'html': 'jupyter_notebook',
174 }
174 }
175
175
176 # Name -> (include, exclude, dependencies_met)
176 # Name -> (include, exclude, dependencies_met)
177 test_sections = {n:TestSection(n, [shims.get(n, 'IPython.%s' % n)]) for n in test_group_names}
177 test_sections = {n:TestSection(n, [shims.get(n, 'IPython.%s' % n)]) for n in test_group_names}
178
178
179
179
180 # Exclusions and dependencies
180 # Exclusions and dependencies
181 # ---------------------------
181 # ---------------------------
182
182
183 # core:
183 # core:
184 sec = test_sections['core']
184 sec = test_sections['core']
185 if not have['sqlite3']:
185 if not have['sqlite3']:
186 sec.exclude('tests.test_history')
186 sec.exclude('tests.test_history')
187 sec.exclude('history')
187 sec.exclude('history')
188 if not have['matplotlib']:
188 if not have['matplotlib']:
189 sec.exclude('pylabtools'),
189 sec.exclude('pylabtools'),
190 sec.exclude('tests.test_pylabtools')
190 sec.exclude('tests.test_pylabtools')
191
191
192 # lib:
192 # lib:
193 sec = test_sections['lib']
193 sec = test_sections['lib']
194 if not have['zmq']:
194 if not have['zmq']:
195 sec.exclude('kernel')
195 sec.exclude('kernel')
196 if not have['pygments']:
197 sec.exclude('tests.test_lexers')
196 # We do this unconditionally, so that the test suite doesn't import
198 # We do this unconditionally, so that the test suite doesn't import
197 # gtk, changing the default encoding and masking some unicode bugs.
199 # gtk, changing the default encoding and masking some unicode bugs.
198 sec.exclude('inputhookgtk')
200 sec.exclude('inputhookgtk')
199 # We also do this unconditionally, because wx can interfere with Unix signals.
201 # We also do this unconditionally, because wx can interfere with Unix signals.
200 # There are currently no tests for it anyway.
202 # There are currently no tests for it anyway.
201 sec.exclude('inputhookwx')
203 sec.exclude('inputhookwx')
202 # Testing inputhook will need a lot of thought, to figure out
204 # Testing inputhook will need a lot of thought, to figure out
203 # how to have tests that don't lock up with the gui event
205 # how to have tests that don't lock up with the gui event
204 # loops in the picture
206 # loops in the picture
205 sec.exclude('inputhook')
207 sec.exclude('inputhook')
206
208
207 # testing:
209 # testing:
208 sec = test_sections['testing']
210 sec = test_sections['testing']
209 # These have to be skipped on win32 because they use echo, rm, cd, etc.
211 # These have to be skipped on win32 because they use echo, rm, cd, etc.
210 # See ticket https://github.com/ipython/ipython/issues/87
212 # See ticket https://github.com/ipython/ipython/issues/87
211 if sys.platform == 'win32':
213 if sys.platform == 'win32':
212 sec.exclude('plugin.test_exampleip')
214 sec.exclude('plugin.test_exampleip')
213 sec.exclude('plugin.dtexample')
215 sec.exclude('plugin.dtexample')
214
216
215 # don't run jupyter_console tests found via shim
217 # don't run jupyter_console tests found via shim
216 test_sections['terminal'].exclude('console')
218 test_sections['terminal'].exclude('console')
217
219
218 # extensions:
220 # extensions:
219 sec = test_sections['extensions']
221 sec = test_sections['extensions']
220 # This is deprecated in favour of rpy2
222 # This is deprecated in favour of rpy2
221 sec.exclude('rmagic')
223 sec.exclude('rmagic')
222 # autoreload does some strange stuff, so move it to its own test section
224 # autoreload does some strange stuff, so move it to its own test section
223 sec.exclude('autoreload')
225 sec.exclude('autoreload')
224 sec.exclude('tests.test_autoreload')
226 sec.exclude('tests.test_autoreload')
225 test_sections['autoreload'] = TestSection('autoreload',
227 test_sections['autoreload'] = TestSection('autoreload',
226 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
228 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
227 test_group_names.append('autoreload')
229 test_group_names.append('autoreload')
228
230
229 # html:
231 # html:
230 sec = test_sections['html']
232 sec = test_sections['html']
231 sec.requires('zmq', 'tornado', 'requests', 'sqlite3', 'jsonschema')
233 sec.requires('zmq', 'tornado', 'requests', 'sqlite3', 'jsonschema')
232 # The notebook 'static' directory contains JS, css and other
234 # The notebook 'static' directory contains JS, css and other
233 # files for web serving. Occasionally projects may put a .py
235 # files for web serving. Occasionally projects may put a .py
234 # file in there (MathJax ships a conf.py), so we might as
236 # file in there (MathJax ships a conf.py), so we might as
235 # well play it safe and skip the whole thing.
237 # well play it safe and skip the whole thing.
236 sec.exclude('static')
238 sec.exclude('static')
237 sec.exclude('tasks')
239 sec.exclude('tasks')
238 if not have['jinja2']:
240 if not have['jinja2']:
239 sec.exclude('notebookapp')
241 sec.exclude('notebookapp')
240 if not have['pygments'] or not have['jinja2']:
242 if not have['pygments'] or not have['jinja2']:
241 sec.exclude('nbconvert')
243 sec.exclude('nbconvert')
242 if not have['terminado']:
244 if not have['terminado']:
243 sec.exclude('terminal')
245 sec.exclude('terminal')
244
246
245 # nbconvert:
247 # nbconvert:
246 sec = test_sections['nbconvert']
248 sec = test_sections['nbconvert']
247 sec.requires('pygments', 'jinja2', 'jsonschema', 'mistune')
249 sec.requires('pygments', 'jinja2', 'jsonschema', 'mistune')
248 # Exclude nbconvert directories containing config files used to test.
250 # Exclude nbconvert directories containing config files used to test.
249 # Executing the config files with iptest would cause an exception.
251 # Executing the config files with iptest would cause an exception.
250 sec.exclude('tests.files')
252 sec.exclude('tests.files')
251 sec.exclude('exporters.tests.files')
253 sec.exclude('exporters.tests.files')
252 if not have['tornado']:
254 if not have['tornado']:
253 sec.exclude('nbconvert.post_processors.serve')
255 sec.exclude('nbconvert.post_processors.serve')
254 sec.exclude('nbconvert.post_processors.tests.test_serve')
256 sec.exclude('nbconvert.post_processors.tests.test_serve')
255
257
256
258
257 #-----------------------------------------------------------------------------
259 #-----------------------------------------------------------------------------
258 # Functions and classes
260 # Functions and classes
259 #-----------------------------------------------------------------------------
261 #-----------------------------------------------------------------------------
260
262
261 def check_exclusions_exist():
263 def check_exclusions_exist():
262 from IPython.utils.path import get_ipython_package_dir
264 from IPython.utils.path import get_ipython_package_dir
263 from IPython.utils.warn import warn
265 from IPython.utils.warn import warn
264 parent = os.path.dirname(get_ipython_package_dir())
266 parent = os.path.dirname(get_ipython_package_dir())
265 for sec in test_sections:
267 for sec in test_sections:
266 for pattern in sec.exclusions:
268 for pattern in sec.exclusions:
267 fullpath = pjoin(parent, pattern)
269 fullpath = pjoin(parent, pattern)
268 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
270 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
269 warn("Excluding nonexistent file: %r" % pattern)
271 warn("Excluding nonexistent file: %r" % pattern)
270
272
271
273
272 class ExclusionPlugin(Plugin):
274 class ExclusionPlugin(Plugin):
273 """A nose plugin to effect our exclusions of files and directories.
275 """A nose plugin to effect our exclusions of files and directories.
274 """
276 """
275 name = 'exclusions'
277 name = 'exclusions'
276 score = 3000 # Should come before any other plugins
278 score = 3000 # Should come before any other plugins
277
279
278 def __init__(self, exclude_patterns=None):
280 def __init__(self, exclude_patterns=None):
279 """
281 """
280 Parameters
282 Parameters
281 ----------
283 ----------
282
284
283 exclude_patterns : sequence of strings, optional
285 exclude_patterns : sequence of strings, optional
284 Filenames containing these patterns (as raw strings, not as regular
286 Filenames containing these patterns (as raw strings, not as regular
285 expressions) are excluded from the tests.
287 expressions) are excluded from the tests.
286 """
288 """
287 self.exclude_patterns = exclude_patterns or []
289 self.exclude_patterns = exclude_patterns or []
288 super(ExclusionPlugin, self).__init__()
290 super(ExclusionPlugin, self).__init__()
289
291
290 def options(self, parser, env=os.environ):
292 def options(self, parser, env=os.environ):
291 Plugin.options(self, parser, env)
293 Plugin.options(self, parser, env)
292
294
293 def configure(self, options, config):
295 def configure(self, options, config):
294 Plugin.configure(self, options, config)
296 Plugin.configure(self, options, config)
295 # Override nose trying to disable plugin.
297 # Override nose trying to disable plugin.
296 self.enabled = True
298 self.enabled = True
297
299
298 def wantFile(self, filename):
300 def wantFile(self, filename):
299 """Return whether the given filename should be scanned for tests.
301 """Return whether the given filename should be scanned for tests.
300 """
302 """
301 if any(pat in filename for pat in self.exclude_patterns):
303 if any(pat in filename for pat in self.exclude_patterns):
302 return False
304 return False
303 return None
305 return None
304
306
305 def wantDirectory(self, directory):
307 def wantDirectory(self, directory):
306 """Return whether the given directory should be scanned for tests.
308 """Return whether the given directory should be scanned for tests.
307 """
309 """
308 if any(pat in directory for pat in self.exclude_patterns):
310 if any(pat in directory for pat in self.exclude_patterns):
309 return False
311 return False
310 return None
312 return None
311
313
312
314
313 class StreamCapturer(Thread):
315 class StreamCapturer(Thread):
314 daemon = True # Don't hang if main thread crashes
316 daemon = True # Don't hang if main thread crashes
315 started = False
317 started = False
316 def __init__(self, echo=False):
318 def __init__(self, echo=False):
317 super(StreamCapturer, self).__init__()
319 super(StreamCapturer, self).__init__()
318 self.echo = echo
320 self.echo = echo
319 self.streams = []
321 self.streams = []
320 self.buffer = BytesIO()
322 self.buffer = BytesIO()
321 self.readfd, self.writefd = os.pipe()
323 self.readfd, self.writefd = os.pipe()
322 self.buffer_lock = Lock()
324 self.buffer_lock = Lock()
323 self.stop = Event()
325 self.stop = Event()
324
326
325 def run(self):
327 def run(self):
326 self.started = True
328 self.started = True
327
329
328 while not self.stop.is_set():
330 while not self.stop.is_set():
329 chunk = os.read(self.readfd, 1024)
331 chunk = os.read(self.readfd, 1024)
330
332
331 with self.buffer_lock:
333 with self.buffer_lock:
332 self.buffer.write(chunk)
334 self.buffer.write(chunk)
333 if self.echo:
335 if self.echo:
334 sys.stdout.write(bytes_to_str(chunk))
336 sys.stdout.write(bytes_to_str(chunk))
335
337
336 os.close(self.readfd)
338 os.close(self.readfd)
337 os.close(self.writefd)
339 os.close(self.writefd)
338
340
339 def reset_buffer(self):
341 def reset_buffer(self):
340 with self.buffer_lock:
342 with self.buffer_lock:
341 self.buffer.truncate(0)
343 self.buffer.truncate(0)
342 self.buffer.seek(0)
344 self.buffer.seek(0)
343
345
344 def get_buffer(self):
346 def get_buffer(self):
345 with self.buffer_lock:
347 with self.buffer_lock:
346 return self.buffer.getvalue()
348 return self.buffer.getvalue()
347
349
348 def ensure_started(self):
350 def ensure_started(self):
349 if not self.started:
351 if not self.started:
350 self.start()
352 self.start()
351
353
352 def halt(self):
354 def halt(self):
353 """Safely stop the thread."""
355 """Safely stop the thread."""
354 if not self.started:
356 if not self.started:
355 return
357 return
356
358
357 self.stop.set()
359 self.stop.set()
358 os.write(self.writefd, b'\0') # Ensure we're not locked in a read()
360 os.write(self.writefd, b'\0') # Ensure we're not locked in a read()
359 self.join()
361 self.join()
360
362
361 class SubprocessStreamCapturePlugin(Plugin):
363 class SubprocessStreamCapturePlugin(Plugin):
362 name='subprocstreams'
364 name='subprocstreams'
363 def __init__(self):
365 def __init__(self):
364 Plugin.__init__(self)
366 Plugin.__init__(self)
365 self.stream_capturer = StreamCapturer()
367 self.stream_capturer = StreamCapturer()
366 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
368 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
367 # This is ugly, but distant parts of the test machinery need to be able
369 # This is ugly, but distant parts of the test machinery need to be able
368 # to redirect streams, so we make the object globally accessible.
370 # to redirect streams, so we make the object globally accessible.
369 nose.iptest_stdstreams_fileno = self.get_write_fileno
371 nose.iptest_stdstreams_fileno = self.get_write_fileno
370
372
371 def get_write_fileno(self):
373 def get_write_fileno(self):
372 if self.destination == 'capture':
374 if self.destination == 'capture':
373 self.stream_capturer.ensure_started()
375 self.stream_capturer.ensure_started()
374 return self.stream_capturer.writefd
376 return self.stream_capturer.writefd
375 elif self.destination == 'discard':
377 elif self.destination == 'discard':
376 return os.open(os.devnull, os.O_WRONLY)
378 return os.open(os.devnull, os.O_WRONLY)
377 else:
379 else:
378 return sys.__stdout__.fileno()
380 return sys.__stdout__.fileno()
379
381
380 def configure(self, options, config):
382 def configure(self, options, config):
381 Plugin.configure(self, options, config)
383 Plugin.configure(self, options, config)
382 # Override nose trying to disable plugin.
384 # Override nose trying to disable plugin.
383 if self.destination == 'capture':
385 if self.destination == 'capture':
384 self.enabled = True
386 self.enabled = True
385
387
386 def startTest(self, test):
388 def startTest(self, test):
387 # Reset log capture
389 # Reset log capture
388 self.stream_capturer.reset_buffer()
390 self.stream_capturer.reset_buffer()
389
391
390 def formatFailure(self, test, err):
392 def formatFailure(self, test, err):
391 # Show output
393 # Show output
392 ec, ev, tb = err
394 ec, ev, tb = err
393 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
395 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
394 if captured.strip():
396 if captured.strip():
395 ev = safe_str(ev)
397 ev = safe_str(ev)
396 out = [ev, '>> begin captured subprocess output <<',
398 out = [ev, '>> begin captured subprocess output <<',
397 captured,
399 captured,
398 '>> end captured subprocess output <<']
400 '>> end captured subprocess output <<']
399 return ec, '\n'.join(out), tb
401 return ec, '\n'.join(out), tb
400
402
401 return err
403 return err
402
404
403 formatError = formatFailure
405 formatError = formatFailure
404
406
405 def finalize(self, result):
407 def finalize(self, result):
406 self.stream_capturer.halt()
408 self.stream_capturer.halt()
407
409
408
410
409 def run_iptest():
411 def run_iptest():
410 """Run the IPython test suite using nose.
412 """Run the IPython test suite using nose.
411
413
412 This function is called when this script is **not** called with the form
414 This function is called when this script is **not** called with the form
413 `iptest all`. It simply calls nose with appropriate command line flags
415 `iptest all`. It simply calls nose with appropriate command line flags
414 and accepts all of the standard nose arguments.
416 and accepts all of the standard nose arguments.
415 """
417 """
416 # Apply our monkeypatch to Xunit
418 # Apply our monkeypatch to Xunit
417 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
419 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
418 monkeypatch_xunit()
420 monkeypatch_xunit()
419
421
420 warnings.filterwarnings('ignore',
422 warnings.filterwarnings('ignore',
421 'This will be removed soon. Use IPython.testing.util instead')
423 'This will be removed soon. Use IPython.testing.util instead')
422
424
423 arg1 = sys.argv[1]
425 arg1 = sys.argv[1]
424 if arg1 in test_sections:
426 if arg1 in test_sections:
425 section = test_sections[arg1]
427 section = test_sections[arg1]
426 sys.argv[1:2] = section.includes
428 sys.argv[1:2] = section.includes
427 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
429 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
428 section = test_sections[arg1[8:]]
430 section = test_sections[arg1[8:]]
429 sys.argv[1:2] = section.includes
431 sys.argv[1:2] = section.includes
430 else:
432 else:
431 section = TestSection(arg1, includes=[arg1])
433 section = TestSection(arg1, includes=[arg1])
432
434
433
435
434 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
436 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
435 # We add --exe because of setuptools' imbecility (it
437 # We add --exe because of setuptools' imbecility (it
436 # blindly does chmod +x on ALL files). Nose does the
438 # blindly does chmod +x on ALL files). Nose does the
437 # right thing and it tries to avoid executables,
439 # right thing and it tries to avoid executables,
438 # setuptools unfortunately forces our hand here. This
440 # setuptools unfortunately forces our hand here. This
439 # has been discussed on the distutils list and the
441 # has been discussed on the distutils list and the
440 # setuptools devs refuse to fix this problem!
442 # setuptools devs refuse to fix this problem!
441 '--exe',
443 '--exe',
442 ]
444 ]
443 if '-a' not in argv and '-A' not in argv:
445 if '-a' not in argv and '-A' not in argv:
444 argv = argv + ['-a', '!crash']
446 argv = argv + ['-a', '!crash']
445
447
446 if nose.__version__ >= '0.11':
448 if nose.__version__ >= '0.11':
447 # I don't fully understand why we need this one, but depending on what
449 # I don't fully understand why we need this one, but depending on what
448 # directory the test suite is run from, if we don't give it, 0 tests
450 # directory the test suite is run from, if we don't give it, 0 tests
449 # get run. Specifically, if the test suite is run from the source dir
451 # get run. Specifically, if the test suite is run from the source dir
450 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
452 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
451 # even if the same call done in this directory works fine). It appears
453 # even if the same call done in this directory works fine). It appears
452 # that if the requested package is in the current dir, nose bails early
454 # that if the requested package is in the current dir, nose bails early
453 # by default. Since it's otherwise harmless, leave it in by default
455 # by default. Since it's otherwise harmless, leave it in by default
454 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
456 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
455 argv.append('--traverse-namespace')
457 argv.append('--traverse-namespace')
456
458
457 plugins = [ ExclusionPlugin(section.excludes), KnownFailure(),
459 plugins = [ ExclusionPlugin(section.excludes), KnownFailure(),
458 SubprocessStreamCapturePlugin() ]
460 SubprocessStreamCapturePlugin() ]
459
461
460 # we still have some vestigial doctests in core
462 # we still have some vestigial doctests in core
461 if (section.name.startswith(('core', 'IPython.core'))):
463 if (section.name.startswith(('core', 'IPython.core'))):
462 plugins.append(IPythonDoctest())
464 plugins.append(IPythonDoctest())
463 argv.extend([
465 argv.extend([
464 '--with-ipdoctest',
466 '--with-ipdoctest',
465 '--ipdoctest-tests',
467 '--ipdoctest-tests',
466 '--ipdoctest-extension=txt',
468 '--ipdoctest-extension=txt',
467 ])
469 ])
468
470
469
471
470 # Use working directory set by parent process (see iptestcontroller)
472 # Use working directory set by parent process (see iptestcontroller)
471 if 'IPTEST_WORKING_DIR' in os.environ:
473 if 'IPTEST_WORKING_DIR' in os.environ:
472 os.chdir(os.environ['IPTEST_WORKING_DIR'])
474 os.chdir(os.environ['IPTEST_WORKING_DIR'])
473
475
474 # We need a global ipython running in this process, but the special
476 # We need a global ipython running in this process, but the special
475 # in-process group spawns its own IPython kernels, so for *that* group we
477 # in-process group spawns its own IPython kernels, so for *that* group we
476 # must avoid also opening the global one (otherwise there's a conflict of
478 # must avoid also opening the global one (otherwise there's a conflict of
477 # singletons). Ultimately the solution to this problem is to refactor our
479 # singletons). Ultimately the solution to this problem is to refactor our
478 # assumptions about what needs to be a singleton and what doesn't (app
480 # assumptions about what needs to be a singleton and what doesn't (app
479 # objects should, individual shells shouldn't). But for now, this
481 # objects should, individual shells shouldn't). But for now, this
480 # workaround allows the test suite for the inprocess module to complete.
482 # workaround allows the test suite for the inprocess module to complete.
481 if 'kernel.inprocess' not in section.name:
483 if 'kernel.inprocess' not in section.name:
482 from IPython.testing import globalipapp
484 from IPython.testing import globalipapp
483 globalipapp.start_ipython()
485 globalipapp.start_ipython()
484
486
485 # Now nose can run
487 # Now nose can run
486 TestProgram(argv=argv, addplugins=plugins)
488 TestProgram(argv=argv, addplugins=plugins)
487
489
488 if __name__ == '__main__':
490 if __name__ == '__main__':
489 run_iptest()
491 run_iptest()
490
492
General Comments 0
You need to be logged in to leave comments. Login now