##// 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 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15
16 import subprocess
17 16 import os
18 17 import glob
19 18 import shutil
20 import sys
21 19
22 20 import IPython
23 from IPython.utils.tempdir import TemporaryDirectory
24 from IPython.utils import py3compat
21 from IPython.utils.tempdir import TemporaryWorkingDirectory
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 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 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 35 functions."""
66 36
67 37
@@ -126,30 +96,25 b' class TestsBase(object):'
126 96 text = text.replace(search, replacement)
127 97 return text
128 98
129
130 99 def create_temp_cwd(self, copy_filenames=None):
131 100 temp_dir = TemporaryWorkingDirectory()
132 101
133 102 #Copy the files if requested.
134 if not copy_filenames is None:
103 if copy_filenames is not None:
135 104 self.copy_files_to(copy_filenames)
136 105
137 106 #Return directory handler
138 107 return temp_dir
139 108
140 109
141 def copy_files_to(self, copy_filenames=None, destination=None):
142
143 #Copy test files into the destination directory.
144 if copy_filenames:
145 for pattern in copy_filenames:
146 for match in glob.glob(os.path.join(self._get_files_path(), pattern)):
147 if destination is None:
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)))
110 def copy_files_to(self, copy_filenames, dest='.'):
111 "Copy test files into the destination directory"
112 if not os.path.isdir(dest):
113 os.makedirs(dest)
114 files_path = self._get_files_path()
115 for pattern in copy_filenames:
116 for match in glob.glob(os.path.join(files_path, pattern)):
117 shutil.copyfile(match, os.path.join(dest, os.path.basename(match)))
153 118
154 119
155 120 def _get_files_path(self):
@@ -166,12 +131,8 b' class TestsBase(object):'
166 131 return path
167 132
168 133
169 def call(self, parameters):
170 output = subprocess.Popen(parameters, stdout=subprocess.PIPE).communicate()[0]
171
172 #Convert the output to a string if running Python3
173 if py3compat.PY3:
174 return output.decode('utf-8')
175 else:
176 return output
177 No newline at end of file
134 def call(self, parameters, raise_on_error=True):
135 stdout, stderr, retcode = get_output_error_code(ipy_cmd + parameters)
136 if retcode != 0 and raise_on_error:
137 raise OSError(stderr)
138 return stdout, stderr
@@ -16,7 +16,6 b' Contains tests for the nbconvertapp'
16 16 import os
17 17 from .base import TestsBase
18 18
19 from IPython.utils import py3compat
20 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 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 28 # Classes and functions
@@ -44,7 +37,8 b' class TestNbConvertApp(TestsBase):'
44 37 Will help show if no notebooks are specified?
45 38 """
46 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 44 def test_glob(self):
@@ -52,8 +46,7 b' class TestNbConvertApp(TestsBase):'
52 46 Do search patterns work for notebook names?
53 47 """
54 48 with self.create_temp_cwd(['notebook*.ipynb']):
55 assert not 'error' in self.call([IPYTHON, 'nbconvert',
56 '--to="python"', '--notebooks=["*.ipynb"]']).lower()
49 self.call('nbconvert --to="python" --notebooks=\'["*.ipynb"]\'')
57 50 assert os.path.isfile('notebook1.py')
58 51 assert os.path.isfile('notebook2.py')
59 52
@@ -62,10 +55,10 b' class TestNbConvertApp(TestsBase):'
62 55 """
63 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 59 self.copy_files_to(['notebook*.ipynb'], 'subdir/')
67 assert not 'error' in self.call([IPYTHON, 'nbconvert', '--to="python"',
68 '--notebooks=["%s"]' % os.path.join('subdir', '*.ipynb')]).lower()
60 self.call('nbconvert --to="python" --notebooks='
61 '\'["%s"]\'' % os.path.join('subdir', '*.ipynb'))
69 62 assert os.path.isfile('notebook1.py')
70 63 assert os.path.isfile('notebook2.py')
71 64
@@ -75,32 +68,34 b' class TestNbConvertApp(TestsBase):'
75 68 Do explicit notebook names work?
76 69 """
77 70 with self.create_temp_cwd(['notebook*.ipynb']):
78 assert not 'error' in self.call([IPYTHON, 'nbconvert', '--to="python"',
79 '--notebooks=["notebook2.ipynb"]']).lower()
71 self.call('nbconvert --to="python" --notebooks='
72 '\'["notebook2.ipynb"]\'')
80 73 assert not os.path.isfile('notebook1.py')
81 74 assert os.path.isfile('notebook2.py')
82 75
83 76
84 77 @dec.onlyif_cmds_exist('pdflatex')
78 @dec.onlyif_cmds_exist('pandoc')
85 79 def test_post_processor(self):
86 80 """
87 81 Do post processors work?
88 82 """
89 83 with self.create_temp_cwd(['notebook1.ipynb']):
90 assert not 'error' in self.call([IPYTHON, 'nbconvert', '--to="latex"',
91 'notebook1', '--post="PDF"', '--PDFPostProcessor.verbose=True']).lower()
84 self.call('nbconvert --to="latex" notebook1'
85 ' --post="PDF" --PDFPostProcessor.verbose=True')
92 86 assert os.path.isfile('notebook1.tex')
93 87 print("\n\n\t" + "\n\t".join([f for f in os.listdir('.') if os.path.isfile(f)]) + "\n\n")
94 88 assert os.path.isfile('notebook1.pdf')
95 89
96 90
91 @dec.onlyif_cmds_exist('pandoc')
97 92 def test_template(self):
98 93 """
99 94 Do export templates work?
100 95 """
101 96 with self.create_temp_cwd(['notebook2.ipynb']):
102 assert not 'error' in self.call([IPYTHON, 'nbconvert', '--to=slides',
103 '--notebooks=["notebook2.ipynb"]', '--template=reveal']).lower()
97 self.call('nbconvert --to=slides --notebooks='
98 '\'["notebook2.ipynb"]\' --template=reveal')
104 99 assert os.path.isfile('notebook2.slides.html')
105 100 with open('notebook2.slides.html') as f:
106 101 assert '/reveal.css' in f.read()
@@ -111,8 +106,8 b' class TestNbConvertApp(TestsBase):'
111 106 Can a search pattern be used along with matching explicit notebook names?
112 107 """
113 108 with self.create_temp_cwd(['notebook*.ipynb']):
114 assert not 'error' in self.call([IPYTHON, 'nbconvert', '--to="python"',
115 '--notebooks=["*.ipynb", "notebook1.ipynb", "notebook2.ipynb"]']).lower()
109 self.call('nbconvert --to="python" --notebooks='
110 '\'["*.ipynb","notebook1.ipynb","notebook2.ipynb"]\'')
116 111 assert os.path.isfile('notebook1.py')
117 112 assert os.path.isfile('notebook2.py')
118 113
@@ -122,8 +117,8 b' class TestNbConvertApp(TestsBase):'
122 117 Can explicit notebook names be used and then a matching search pattern?
123 118 """
124 119 with self.create_temp_cwd(['notebook*.ipynb']):
125 assert not 'error' in self.call([IPYTHON, 'nbconvert', '--to="python"',
126 '--notebooks=["notebook1.ipynb", "notebook2.ipynb", "*.ipynb"]']).lower()
120 self.call('nbconvert --to="python" --notebooks='
121 '\'["notebook1.ipynb","notebook2.ipynb","*.ipynb"]\'')
127 122 assert os.path.isfile('notebook1.py')
128 123 assert os.path.isfile('notebook2.py')
129 124
@@ -133,7 +128,7 b' class TestNbConvertApp(TestsBase):'
133 128 Does the default config work?
134 129 """
135 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 132 assert os.path.isfile('notebook1.py')
138 133 assert not os.path.isfile('notebook2.py')
139 134
@@ -142,8 +137,9 b' class TestNbConvertApp(TestsBase):'
142 137 """
143 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 142 'override.py']):
147 assert not 'error' in self.call([IPYTHON, 'nbconvert', '--config="override.py"']).lower()
143 self.call('nbconvert --config="override.py"')
148 144 assert not os.path.isfile('notebook1.py')
149 145 assert os.path.isfile('notebook2.py')
@@ -19,7 +19,6 b' from __future__ import absolute_import'
19 19 #-----------------------------------------------------------------------------
20 20
21 21 import os
22 import pipes
23 22 import re
24 23 import sys
25 24 import tempfile
@@ -38,7 +37,6 b' except ImportError:'
38 37 has_nose = False
39 38
40 39 from IPython.config.loader import Config
41 from IPython.utils.process import getoutputerror
42 40 from IPython.utils.text import list_strings
43 41 from IPython.utils.io import temp_pyfile, Tee
44 42 from IPython.utils import py3compat
@@ -156,6 +154,28 b' def default_config():'
156 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 179 def ipexec(fname, options=None):
160 180 """Utility to call 'ipython filename'.
161 181
@@ -186,14 +206,9 b' def ipexec(fname, options=None):'
186 206 ]
187 207 cmdargs = default_argv() + prompt_opts + options
188 208
189 _ip = get_ipython()
190 209 test_dir = os.path.dirname(__file__)
191
192 # FIXME: remove workaround for 2.6 support
193 if sys.version_info[:2] > (2,6):
194 ipython_cmd = [sys.executable, "-m", "IPython"]
195 else:
196 ipython_cmd = ["ipython"]
210
211 ipython_cmd = get_ipython_cmd()
197 212 # Absolute path for filename
198 213 full_fname = os.path.join(test_dir, fname)
199 214 full_cmd = ipython_cmd + cmdargs + [full_fname]
@@ -139,13 +139,31 b' def getoutputerror(cmd):'
139 139 stdout : str
140 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 163 if out_err is None:
145 return '', ''
164 return '', '', p.returncode
146 165 out, err = out_err
147 return py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
148
166 return py3compat.bytes_to_str(out), py3compat.bytes_to_str(err), p.returncode
149 167
150 168 def arg_split(s, posix=False, strict=True):
151 169 """Split a command line's arguments in a shell-like manner.
@@ -27,7 +27,7 b' else:'
27 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 33 # Code
@@ -104,3 +104,29 b' class NamedFileInTemporaryDirectory(object):'
104 104
105 105 def __exit__(self, type, value, traceback):
106 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 21 import nose.tools as nt
22 22
23 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 26 from IPython.testing import decorators as dec
26 27 from IPython.testing import tools as tt
27 28
@@ -132,3 +133,14 b' class SubProcessTestCase(TestCase, tt.TempFileMixin):'
132 133 out, err = getoutputerror('%s "%s"' % (python, self.fname))
133 134 self.assertEqual(out, 'on stdout')
134 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 8 import os
9 9
10 10 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
11 from IPython.utils.tempdir import TemporaryWorkingDirectory
11 12
12 13
13 14 def test_named_file_in_temporary_directory():
@@ -18,3 +19,10 b' def test_named_file_in_temporary_directory():'
18 19 file.write(b'test')
19 20 assert file.closed
20 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