##// END OF EJS Templates
Merge pull request #3834 from ivanov/nbconvert-better-tests...
Min RK -
r11892:fdb8764a merge
parent child Browse files
Show More
@@ -13,55 +13,25 b' Contains base test class for nbconvert'
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 import subprocess
17 import os
16 import os
18 import glob
17 import glob
19 import shutil
18 import shutil
20 import sys
21
19
22 import IPython
20 import IPython
23 from IPython.utils.tempdir import TemporaryDirectory
21 from IPython.utils.tempdir import TemporaryWorkingDirectory
24 from IPython.utils import py3compat
22 from IPython.utils.process import get_output_error_code
23 from IPython.testing.tools import get_ipython_cmd
24
25 # a trailing space allows for simpler concatenation with the other arguments
26 ipy_cmd = get_ipython_cmd(as_string=True) + " "
25
27
26 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
27 # Classes and functions
29 # Classes and functions
28 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
29
31
30 class TemporaryWorkingDirectory(TemporaryDirectory):
31 """
32 Creates a temporary directory and sets the cwd to that directory.
33 Automatically reverts to previous cwd upon cleanup.
34 Usage example:
35
36 with TemporaryWorakingDirectory() as tmpdir:
37 ...
38 """
39
40 def __init__(self, **kw):
41 """
42 Constructor
43 """
44 super(TemporaryWorkingDirectory, self).__init__(**kw)
45
46 #Change cwd to new temp dir. Remember old cwd.
47 self.old_wd = os.getcwd()
48 os.chdir(self.name)
49
50
51 def cleanup(self):
52 """
53 Destructor
54 """
55
56 #Revert to old cwd.
57 os.chdir(self.old_wd)
58
59 #Cleanup
60 super(TemporaryWorkingDirectory, self).cleanup()
61
62
32
63 class TestsBase(object):
33 class TestsBase(object):
64 """Base tests class. Contains usefull fuzzy comparison and nbconvert
34 """Base tests class. Contains useful fuzzy comparison and nbconvert
65 functions."""
35 functions."""
66
36
67
37
@@ -126,30 +96,25 b' class TestsBase(object):'
126 text = text.replace(search, replacement)
96 text = text.replace(search, replacement)
127 return text
97 return text
128
98
129
130 def create_temp_cwd(self, copy_filenames=None):
99 def create_temp_cwd(self, copy_filenames=None):
131 temp_dir = TemporaryWorkingDirectory()
100 temp_dir = TemporaryWorkingDirectory()
132
101
133 #Copy the files if requested.
102 #Copy the files if requested.
134 if not copy_filenames is None:
103 if copy_filenames is not None:
135 self.copy_files_to(copy_filenames)
104 self.copy_files_to(copy_filenames)
136
105
137 #Return directory handler
106 #Return directory handler
138 return temp_dir
107 return temp_dir
139
108
140
109
141 def copy_files_to(self, copy_filenames=None, destination=None):
110 def copy_files_to(self, copy_filenames, dest='.'):
142
111 "Copy test files into the destination directory"
143 #Copy test files into the destination directory.
112 if not os.path.isdir(dest):
144 if copy_filenames:
113 os.makedirs(dest)
114 files_path = self._get_files_path()
145 for pattern in copy_filenames:
115 for pattern in copy_filenames:
146 for match in glob.glob(os.path.join(self._get_files_path(), pattern)):
116 for match in glob.glob(os.path.join(files_path, pattern)):
147 if destination is None:
117 shutil.copyfile(match, os.path.join(dest, os.path.basename(match)))
148 shutil.copyfile(match, os.path.basename(match))
149 else:
150 if not os.path.isdir(destination):
151 os.makedirs(destination)
152 shutil.copyfile(match, os.path.join(destination, os.path.basename(match)))
153
118
154
119
155 def _get_files_path(self):
120 def _get_files_path(self):
@@ -166,12 +131,8 b' class TestsBase(object):'
166 return path
131 return path
167
132
168
133
169 def call(self, parameters):
134 def call(self, parameters, raise_on_error=True):
170 output = subprocess.Popen(parameters, stdout=subprocess.PIPE).communicate()[0]
135 stdout, stderr, retcode = get_output_error_code(ipy_cmd + parameters)
171
136 if retcode != 0 and raise_on_error:
172 #Convert the output to a string if running Python3
137 raise OSError(stderr)
173 if py3compat.PY3:
138 return stdout, stderr
174 return output.decode('utf-8')
175 else:
176 return output
177 No newline at end of file
@@ -16,7 +16,6 b' Contains tests for the nbconvertapp'
16 import os
16 import os
17 from .base import TestsBase
17 from .base import TestsBase
18
18
19 from IPython.utils import py3compat
20 from IPython.testing import decorators as dec
19 from IPython.testing import decorators as dec
21
20
22
21
@@ -24,12 +23,6 b' from IPython.testing import decorators as dec'
24 # Constants
23 # Constants
25 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
26
25
27 # Define ipython commandline name
28 if py3compat.PY3:
29 IPYTHON = 'ipython3'
30 else:
31 IPYTHON = 'ipython'
32
33
26
34 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
35 # Classes and functions
28 # Classes and functions
@@ -44,7 +37,8 b' class TestNbConvertApp(TestsBase):'
44 Will help show if no notebooks are specified?
37 Will help show if no notebooks are specified?
45 """
38 """
46 with self.create_temp_cwd():
39 with self.create_temp_cwd():
47 assert "see '--help-all'" in self.call([IPYTHON, 'nbconvert'])
40 out, err = self.call('nbconvert', raise_on_error=False)
41 assert "see '--help-all'" in out
48
42
49
43
50 def test_glob(self):
44 def test_glob(self):
@@ -52,8 +46,7 b' class TestNbConvertApp(TestsBase):'
52 Do search patterns work for notebook names?
46 Do search patterns work for notebook names?
53 """
47 """
54 with self.create_temp_cwd(['notebook*.ipynb']):
48 with self.create_temp_cwd(['notebook*.ipynb']):
55 assert not 'error' in self.call([IPYTHON, 'nbconvert',
49 self.call('nbconvert --to="python" --notebooks=\'["*.ipynb"]\'')
56 '--to="python"', '--notebooks=["*.ipynb"]']).lower()
57 assert os.path.isfile('notebook1.py')
50 assert os.path.isfile('notebook1.py')
58 assert os.path.isfile('notebook2.py')
51 assert os.path.isfile('notebook2.py')
59
52
@@ -62,10 +55,10 b' class TestNbConvertApp(TestsBase):'
62 """
55 """
63 Do search patterns work for subdirectory notebook names?
56 Do search patterns work for subdirectory notebook names?
64 """
57 """
65 with self.create_temp_cwd() as cwd:
58 with self.create_temp_cwd():
66 self.copy_files_to(['notebook*.ipynb'], 'subdir/')
59 self.copy_files_to(['notebook*.ipynb'], 'subdir/')
67 assert not 'error' in self.call([IPYTHON, 'nbconvert', '--to="python"',
60 self.call('nbconvert --to="python" --notebooks='
68 '--notebooks=["%s"]' % os.path.join('subdir', '*.ipynb')]).lower()
61 '\'["%s"]\'' % os.path.join('subdir', '*.ipynb'))
69 assert os.path.isfile('notebook1.py')
62 assert os.path.isfile('notebook1.py')
70 assert os.path.isfile('notebook2.py')
63 assert os.path.isfile('notebook2.py')
71
64
@@ -75,32 +68,34 b' class TestNbConvertApp(TestsBase):'
75 Do explicit notebook names work?
68 Do explicit notebook names work?
76 """
69 """
77 with self.create_temp_cwd(['notebook*.ipynb']):
70 with self.create_temp_cwd(['notebook*.ipynb']):
78 assert not 'error' in self.call([IPYTHON, 'nbconvert', '--to="python"',
71 self.call('nbconvert --to="python" --notebooks='
79 '--notebooks=["notebook2.ipynb"]']).lower()
72 '\'["notebook2.ipynb"]\'')
80 assert not os.path.isfile('notebook1.py')
73 assert not os.path.isfile('notebook1.py')
81 assert os.path.isfile('notebook2.py')
74 assert os.path.isfile('notebook2.py')
82
75
83
76
84 @dec.onlyif_cmds_exist('pdflatex')
77 @dec.onlyif_cmds_exist('pdflatex')
78 @dec.onlyif_cmds_exist('pandoc')
85 def test_post_processor(self):
79 def test_post_processor(self):
86 """
80 """
87 Do post processors work?
81 Do post processors work?
88 """
82 """
89 with self.create_temp_cwd(['notebook1.ipynb']):
83 with self.create_temp_cwd(['notebook1.ipynb']):
90 assert not 'error' in self.call([IPYTHON, 'nbconvert', '--to="latex"',
84 self.call('nbconvert --to="latex" notebook1'
91 'notebook1', '--post="PDF"', '--PDFPostProcessor.verbose=True']).lower()
85 ' --post="PDF" --PDFPostProcessor.verbose=True')
92 assert os.path.isfile('notebook1.tex')
86 assert os.path.isfile('notebook1.tex')
93 print("\n\n\t" + "\n\t".join([f for f in os.listdir('.') if os.path.isfile(f)]) + "\n\n")
87 print("\n\n\t" + "\n\t".join([f for f in os.listdir('.') if os.path.isfile(f)]) + "\n\n")
94 assert os.path.isfile('notebook1.pdf')
88 assert os.path.isfile('notebook1.pdf')
95
89
96
90
91 @dec.onlyif_cmds_exist('pandoc')
97 def test_template(self):
92 def test_template(self):
98 """
93 """
99 Do export templates work?
94 Do export templates work?
100 """
95 """
101 with self.create_temp_cwd(['notebook2.ipynb']):
96 with self.create_temp_cwd(['notebook2.ipynb']):
102 assert not 'error' in self.call([IPYTHON, 'nbconvert', '--to=slides',
97 self.call('nbconvert --to=slides --notebooks='
103 '--notebooks=["notebook2.ipynb"]', '--template=reveal']).lower()
98 '\'["notebook2.ipynb"]\' --template=reveal')
104 assert os.path.isfile('notebook2.slides.html')
99 assert os.path.isfile('notebook2.slides.html')
105 with open('notebook2.slides.html') as f:
100 with open('notebook2.slides.html') as f:
106 assert '/reveal.css' in f.read()
101 assert '/reveal.css' in f.read()
@@ -111,8 +106,8 b' class TestNbConvertApp(TestsBase):'
111 Can a search pattern be used along with matching explicit notebook names?
106 Can a search pattern be used along with matching explicit notebook names?
112 """
107 """
113 with self.create_temp_cwd(['notebook*.ipynb']):
108 with self.create_temp_cwd(['notebook*.ipynb']):
114 assert not 'error' in self.call([IPYTHON, 'nbconvert', '--to="python"',
109 self.call('nbconvert --to="python" --notebooks='
115 '--notebooks=["*.ipynb", "notebook1.ipynb", "notebook2.ipynb"]']).lower()
110 '\'["*.ipynb","notebook1.ipynb","notebook2.ipynb"]\'')
116 assert os.path.isfile('notebook1.py')
111 assert os.path.isfile('notebook1.py')
117 assert os.path.isfile('notebook2.py')
112 assert os.path.isfile('notebook2.py')
118
113
@@ -122,8 +117,8 b' class TestNbConvertApp(TestsBase):'
122 Can explicit notebook names be used and then a matching search pattern?
117 Can explicit notebook names be used and then a matching search pattern?
123 """
118 """
124 with self.create_temp_cwd(['notebook*.ipynb']):
119 with self.create_temp_cwd(['notebook*.ipynb']):
125 assert not 'error' in self.call([IPYTHON, 'nbconvert', '--to="python"',
120 self.call('nbconvert --to="python" --notebooks='
126 '--notebooks=["notebook1.ipynb", "notebook2.ipynb", "*.ipynb"]']).lower()
121 '\'["notebook1.ipynb","notebook2.ipynb","*.ipynb"]\'')
127 assert os.path.isfile('notebook1.py')
122 assert os.path.isfile('notebook1.py')
128 assert os.path.isfile('notebook2.py')
123 assert os.path.isfile('notebook2.py')
129
124
@@ -133,7 +128,7 b' class TestNbConvertApp(TestsBase):'
133 Does the default config work?
128 Does the default config work?
134 """
129 """
135 with self.create_temp_cwd(['notebook*.ipynb', 'ipython_nbconvert_config.py']):
130 with self.create_temp_cwd(['notebook*.ipynb', 'ipython_nbconvert_config.py']):
136 assert not 'error' in self.call([IPYTHON, 'nbconvert']).lower()
131 self.call('nbconvert')
137 assert os.path.isfile('notebook1.py')
132 assert os.path.isfile('notebook1.py')
138 assert not os.path.isfile('notebook2.py')
133 assert not os.path.isfile('notebook2.py')
139
134
@@ -142,8 +137,9 b' class TestNbConvertApp(TestsBase):'
142 """
137 """
143 Can the default config be overriden?
138 Can the default config be overriden?
144 """
139 """
145 with self.create_temp_cwd(['notebook*.ipynb', 'ipython_nbconvert_config.py',
140 with self.create_temp_cwd(['notebook*.ipynb',
141 'ipython_nbconvert_config.py',
146 'override.py']):
142 'override.py']):
147 assert not 'error' in self.call([IPYTHON, 'nbconvert', '--config="override.py"']).lower()
143 self.call('nbconvert --config="override.py"')
148 assert not os.path.isfile('notebook1.py')
144 assert not os.path.isfile('notebook1.py')
149 assert os.path.isfile('notebook2.py')
145 assert os.path.isfile('notebook2.py')
@@ -19,7 +19,6 b' from __future__ import absolute_import'
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 import os
21 import os
22 import pipes
23 import re
22 import re
24 import sys
23 import sys
25 import tempfile
24 import tempfile
@@ -38,7 +37,6 b' except ImportError:'
38 has_nose = False
37 has_nose = False
39
38
40 from IPython.config.loader import Config
39 from IPython.config.loader import Config
41 from IPython.utils.process import getoutputerror
42 from IPython.utils.text import list_strings
40 from IPython.utils.text import list_strings
43 from IPython.utils.io import temp_pyfile, Tee
41 from IPython.utils.io import temp_pyfile, Tee
44 from IPython.utils import py3compat
42 from IPython.utils import py3compat
@@ -156,6 +154,28 b' def default_config():'
156 return config
154 return config
157
155
158
156
157 def get_ipython_cmd(as_string=False):
158 """
159 Return appropriate IPython command line name. By default, this will return
160 a list that can be used with subprocess.Popen, for example, but passing
161 `as_string=True` allows for returning the IPython command as a string.
162
163 Parameters
164 ----------
165 as_string: bool
166 Flag to allow to return the command as a string.
167 """
168 # FIXME: remove workaround for 2.6 support
169 if sys.version_info[:2] > (2,6):
170 ipython_cmd = [sys.executable, "-m", "IPython"]
171 else:
172 ipython_cmd = ["ipython"]
173
174 if as_string:
175 ipython_cmd = " ".join(ipython_cmd)
176
177 return ipython_cmd
178
159 def ipexec(fname, options=None):
179 def ipexec(fname, options=None):
160 """Utility to call 'ipython filename'.
180 """Utility to call 'ipython filename'.
161
181
@@ -186,14 +206,9 b' def ipexec(fname, options=None):'
186 ]
206 ]
187 cmdargs = default_argv() + prompt_opts + options
207 cmdargs = default_argv() + prompt_opts + options
188
208
189 _ip = get_ipython()
190 test_dir = os.path.dirname(__file__)
209 test_dir = os.path.dirname(__file__)
191
210
192 # FIXME: remove workaround for 2.6 support
211 ipython_cmd = get_ipython_cmd()
193 if sys.version_info[:2] > (2,6):
194 ipython_cmd = [sys.executable, "-m", "IPython"]
195 else:
196 ipython_cmd = ["ipython"]
197 # Absolute path for filename
212 # Absolute path for filename
198 full_fname = os.path.join(test_dir, fname)
213 full_fname = os.path.join(test_dir, fname)
199 full_cmd = ipython_cmd + cmdargs + [full_fname]
214 full_cmd = ipython_cmd + cmdargs + [full_fname]
@@ -139,13 +139,31 b' def getoutputerror(cmd):'
139 stdout : str
139 stdout : str
140 stderr : str
140 stderr : str
141 """
141 """
142 return get_output_error_code(cmd)[:2]
142
143
143 out_err = process_handler(cmd, lambda p: p.communicate())
144 def get_output_error_code(cmd):
145 """Return (standard output, standard error, return code) of executing cmd
146 in a shell.
147
148 Accepts the same arguments as os.system().
149
150 Parameters
151 ----------
152 cmd : str
153 A command to be executed in the system shell.
154
155 Returns
156 -------
157 stdout : str
158 stderr : str
159 returncode: int
160 """
161
162 out_err, p = process_handler(cmd, lambda p: (p.communicate(), p))
144 if out_err is None:
163 if out_err is None:
145 return '', ''
164 return '', '', p.returncode
146 out, err = out_err
165 out, err = out_err
147 return py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
166 return py3compat.bytes_to_str(out), py3compat.bytes_to_str(err), p.returncode
148
149
167
150 def arg_split(s, posix=False, strict=True):
168 def arg_split(s, posix=False, strict=True):
151 """Split a command line's arguments in a shell-like manner.
169 """Split a command line's arguments in a shell-like manner.
@@ -27,7 +27,7 b' else:'
27 from ._process_posix import _find_cmd, system, getoutput, arg_split
27 from ._process_posix import _find_cmd, system, getoutput, arg_split
28
28
29
29
30 from ._process_common import getoutputerror
30 from ._process_common import getoutputerror, get_output_error_code
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Code
33 # Code
@@ -104,3 +104,29 b' class NamedFileInTemporaryDirectory(object):'
104
104
105 def __exit__(self, type, value, traceback):
105 def __exit__(self, type, value, traceback):
106 self.cleanup()
106 self.cleanup()
107
108
109 class TemporaryWorkingDirectory(TemporaryDirectory):
110 """
111 Creates a temporary directory and sets the cwd to that directory.
112 Automatically reverts to previous cwd upon cleanup.
113 Usage example:
114
115 with TemporaryWorakingDirectory() as tmpdir:
116 ...
117 """
118
119 def __init__(self, **kw):
120 super(TemporaryWorkingDirectory, self).__init__(**kw)
121
122 #Change cwd to new temp dir. Remember old cwd.
123 self.old_wd = _os.getcwd()
124 _os.chdir(self.name)
125
126
127 def cleanup(self):
128 #Revert to old cwd.
129 _os.chdir(self.old_wd)
130
131 #Cleanup
132 super(TemporaryWorkingDirectory, self).cleanup()
@@ -21,7 +21,8 b' from unittest import TestCase'
21 import nose.tools as nt
21 import nose.tools as nt
22
22
23 from IPython.utils.process import (find_cmd, FindCmdError, arg_split,
23 from IPython.utils.process import (find_cmd, FindCmdError, arg_split,
24 system, getoutput, getoutputerror)
24 system, getoutput, getoutputerror,
25 get_output_error_code)
25 from IPython.testing import decorators as dec
26 from IPython.testing import decorators as dec
26 from IPython.testing import tools as tt
27 from IPython.testing import tools as tt
27
28
@@ -132,3 +133,14 b' class SubProcessTestCase(TestCase, tt.TempFileMixin):'
132 out, err = getoutputerror('%s "%s"' % (python, self.fname))
133 out, err = getoutputerror('%s "%s"' % (python, self.fname))
133 self.assertEqual(out, 'on stdout')
134 self.assertEqual(out, 'on stdout')
134 self.assertEqual(err, 'on stderr')
135 self.assertEqual(err, 'on stderr')
136
137 def test_get_output_error_code(self):
138 quiet_exit = '%s -c "import sys; sys.exit(1)"' % python
139 out, err, code = get_output_error_code(quiet_exit)
140 self.assertEqual(out, '')
141 self.assertEqual(err, '')
142 self.assertEqual(code, 1)
143 out, err, code = get_output_error_code('%s "%s"' % (python, self.fname))
144 self.assertEqual(out, 'on stdout')
145 self.assertEqual(err, 'on stderr')
146 self.assertEqual(code, 0)
@@ -8,6 +8,7 b''
8 import os
8 import os
9
9
10 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
10 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
11 from IPython.utils.tempdir import TemporaryWorkingDirectory
11
12
12
13
13 def test_named_file_in_temporary_directory():
14 def test_named_file_in_temporary_directory():
@@ -18,3 +19,10 b' def test_named_file_in_temporary_directory():'
18 file.write(b'test')
19 file.write(b'test')
19 assert file.closed
20 assert file.closed
20 assert not os.path.exists(name)
21 assert not os.path.exists(name)
22
23 def test_temporary_working_directory():
24 with TemporaryWorkingDirectory() as dir:
25 assert os.path.exists(dir)
26 assert os.path.abspath(os.curdir) == dir
27 assert not os.path.exists(dir)
28 assert os.path.abspath(os.curdir) != dir
General Comments 0
You need to be logged in to leave comments. Login now