##// END OF EJS Templates
Merge pull request #3834 from ivanov/nbconvert-better-tests...
Min RK -
r11892:fdb8764a merge
parent child Browse files
Show More
@@ -1,177 +1,138 b''
1 1 """
2 2 Contains base test class for nbconvert
3 3 """
4 4 #-----------------------------------------------------------------------------
5 5 #Copyright (c) 2013, the IPython Development Team.
6 6 #
7 7 #Distributed under the terms of the Modified BSD License.
8 8 #
9 9 #The full license is in the file COPYING.txt, distributed with this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12 #-----------------------------------------------------------------------------
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
68 38 def fuzzy_compare(self, a, b, newlines_are_spaces=True, tabs_are_spaces=True,
69 39 fuzzy_spacing=True, ignore_spaces=False,
70 40 ignore_newlines=False, case_sensitive=False):
71 41 """
72 42 Performs a fuzzy comparison of two strings. A fuzzy comparison is a
73 43 comparison that ignores insignificant differences in the two comparands.
74 44 The significance of certain differences can be specified via the keyword
75 45 parameters of this method.
76 46 """
77 47
78 48 if newlines_are_spaces:
79 49 a = a.replace('\n', ' ')
80 50 b = b.replace('\n', ' ')
81 51
82 52 if tabs_are_spaces:
83 53 a = a.replace('\t', ' ')
84 54 b = b.replace('\t', ' ')
85 55
86 56 if ignore_spaces:
87 57 a = a.replace(' ', '')
88 58 b = b.replace(' ', '')
89 59
90 60 if fuzzy_spacing:
91 61 a = self.recursive_replace(a, ' ', ' ')
92 62 b = self.recursive_replace(b, ' ', ' ')
93 63
94 64 if ignore_newlines:
95 65 a = a.replace('\n', '')
96 66 b = b.replace('\n', '')
97 67
98 68 if not case_sensitive:
99 69 a = a.lower()
100 70 b = b.lower()
101 71
102 72 return a == b
103 73
104 74
105 75 def recursive_replace(self, text, search, replacement):
106 76 """
107 77 Performs a recursive replacement operation. Replaces all instances
108 78 of a search string in a text string with a replacement string until
109 79 the search string no longer exists. Recursion is needed because the
110 80 replacement string may generate additional search strings.
111 81
112 82 For example:
113 83 Replace "ii" with "i" in the string "Hiiii" yields "Hii"
114 84 Another replacement yields "Hi" (the desired output)
115 85
116 86 Parameters:
117 87 -----------
118 88 text : string
119 89 Text to replace in.
120 90 search : string
121 91 String to search for within "text"
122 92 replacement : string
123 93 String to replace "search" with
124 94 """
125 95 while search in text:
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):
156 121
157 122 #Get the relative path to this module in the IPython directory.
158 123 names = self.__module__.split('.')[1:-1]
159 124 names.append('files')
160 125
161 126 #Build a path using the IPython directory and the relative path we just
162 127 #found.
163 128 path = IPython.__path__[0]
164 129 for name in names:
165 130 path = os.path.join(path, name)
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
@@ -1,149 +1,145 b''
1 1 """
2 2 Contains tests for the nbconvertapp
3 3 """
4 4 #-----------------------------------------------------------------------------
5 5 #Copyright (c) 2013, the IPython Development Team.
6 6 #
7 7 #Distributed under the terms of the Modified BSD License.
8 8 #
9 9 #The full license is in the file COPYING.txt, distributed with this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15
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
23 22 #-----------------------------------------------------------------------------
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
36 29 #-----------------------------------------------------------------------------
37 30
38 31 class TestNbConvertApp(TestsBase):
39 32 """Collection of NbConvertApp tests"""
40 33
41 34
42 35 def test_notebook_help(self):
43 36 """
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):
51 45 """
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
60 53
61 54 def test_glob_subdir(self):
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
72 65
73 66 def test_explicit(self):
74 67 """
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()
107 102
108 103
109 104 def test_glob_explicit(self):
110 105 """
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
119 114
120 115 def test_explicit_glob(self):
121 116 """
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
130 125
131 126 def test_default_config(self):
132 127 """
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
140 135
141 136 def test_override_config(self):
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')
@@ -1,399 +1,414 b''
1 1 """Generic testing tools.
2 2
3 3 Authors
4 4 -------
5 5 - Fernando Perez <Fernando.Perez@berkeley.edu>
6 6 """
7 7
8 8 from __future__ import absolute_import
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Copyright (C) 2009-2011 The IPython Development Team
12 12 #
13 13 # Distributed under the terms of the BSD License. The full license is in
14 14 # the file COPYING, distributed as part of this software.
15 15 #-----------------------------------------------------------------------------
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Imports
19 19 #-----------------------------------------------------------------------------
20 20
21 21 import os
22 import pipes
23 22 import re
24 23 import sys
25 24 import tempfile
26 25
27 26 from contextlib import contextmanager
28 27 from io import StringIO
29 28 from subprocess import Popen, PIPE
30 29
31 30 try:
32 31 # These tools are used by parts of the runtime, so we make the nose
33 32 # dependency optional at this point. Nose is a hard dependency to run the
34 33 # test suite, but NOT to use ipython itself.
35 34 import nose.tools as nt
36 35 has_nose = True
37 36 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
45 43 from IPython.utils.encoding import DEFAULT_ENCODING
46 44
47 45 from . import decorators as dec
48 46 from . import skipdoctest
49 47
50 48 #-----------------------------------------------------------------------------
51 49 # Functions and classes
52 50 #-----------------------------------------------------------------------------
53 51
54 52 # The docstring for full_path doctests differently on win32 (different path
55 53 # separator) so just skip the doctest there. The example remains informative.
56 54 doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco
57 55
58 56 @doctest_deco
59 57 def full_path(startPath,files):
60 58 """Make full paths for all the listed files, based on startPath.
61 59
62 60 Only the base part of startPath is kept, since this routine is typically
63 61 used with a script's __file__ variable as startPath. The base of startPath
64 62 is then prepended to all the listed files, forming the output list.
65 63
66 64 Parameters
67 65 ----------
68 66 startPath : string
69 67 Initial path to use as the base for the results. This path is split
70 68 using os.path.split() and only its first component is kept.
71 69
72 70 files : string or list
73 71 One or more files.
74 72
75 73 Examples
76 74 --------
77 75
78 76 >>> full_path('/foo/bar.py',['a.txt','b.txt'])
79 77 ['/foo/a.txt', '/foo/b.txt']
80 78
81 79 >>> full_path('/foo',['a.txt','b.txt'])
82 80 ['/a.txt', '/b.txt']
83 81
84 82 If a single file is given, the output is still a list:
85 83 >>> full_path('/foo','a.txt')
86 84 ['/a.txt']
87 85 """
88 86
89 87 files = list_strings(files)
90 88 base = os.path.split(startPath)[0]
91 89 return [ os.path.join(base,f) for f in files ]
92 90
93 91
94 92 def parse_test_output(txt):
95 93 """Parse the output of a test run and return errors, failures.
96 94
97 95 Parameters
98 96 ----------
99 97 txt : str
100 98 Text output of a test run, assumed to contain a line of one of the
101 99 following forms::
102 100
103 101 'FAILED (errors=1)'
104 102 'FAILED (failures=1)'
105 103 'FAILED (errors=1, failures=1)'
106 104
107 105 Returns
108 106 -------
109 107 nerr, nfail: number of errors and failures.
110 108 """
111 109
112 110 err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE)
113 111 if err_m:
114 112 nerr = int(err_m.group(1))
115 113 nfail = 0
116 114 return nerr, nfail
117 115
118 116 fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE)
119 117 if fail_m:
120 118 nerr = 0
121 119 nfail = int(fail_m.group(1))
122 120 return nerr, nfail
123 121
124 122 both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt,
125 123 re.MULTILINE)
126 124 if both_m:
127 125 nerr = int(both_m.group(1))
128 126 nfail = int(both_m.group(2))
129 127 return nerr, nfail
130 128
131 129 # If the input didn't match any of these forms, assume no error/failures
132 130 return 0, 0
133 131
134 132
135 133 # So nose doesn't think this is a test
136 134 parse_test_output.__test__ = False
137 135
138 136
139 137 def default_argv():
140 138 """Return a valid default argv for creating testing instances of ipython"""
141 139
142 140 return ['--quick', # so no config file is loaded
143 141 # Other defaults to minimize side effects on stdout
144 142 '--colors=NoColor', '--no-term-title','--no-banner',
145 143 '--autocall=0']
146 144
147 145
148 146 def default_config():
149 147 """Return a config object with good defaults for testing."""
150 148 config = Config()
151 149 config.TerminalInteractiveShell.colors = 'NoColor'
152 150 config.TerminalTerminalInteractiveShell.term_title = False,
153 151 config.TerminalInteractiveShell.autocall = 0
154 152 config.HistoryManager.hist_file = tempfile.mktemp(u'test_hist.sqlite')
155 153 config.HistoryManager.db_cache_size = 10000
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
162 182 Starts IPython with a minimal and safe configuration to make startup as fast
163 183 as possible.
164 184
165 185 Note that this starts IPython in a subprocess!
166 186
167 187 Parameters
168 188 ----------
169 189 fname : str
170 190 Name of file to be executed (should have .py or .ipy extension).
171 191
172 192 options : optional, list
173 193 Extra command-line flags to be passed to IPython.
174 194
175 195 Returns
176 196 -------
177 197 (stdout, stderr) of ipython subprocess.
178 198 """
179 199 if options is None: options = []
180 200
181 201 # For these subprocess calls, eliminate all prompt printing so we only see
182 202 # output from script execution
183 203 prompt_opts = [ '--PromptManager.in_template=""',
184 204 '--PromptManager.in2_template=""',
185 205 '--PromptManager.out_template=""'
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]
200 215 p = Popen(full_cmd, stdout=PIPE, stderr=PIPE)
201 216 out, err = p.communicate()
202 217 out, err = py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
203 218 # `import readline` causes 'ESC[?1034h' to be output sometimes,
204 219 # so strip that out before doing comparisons
205 220 if out:
206 221 out = re.sub(r'\x1b\[[^h]+h', '', out)
207 222 return out, err
208 223
209 224
210 225 def ipexec_validate(fname, expected_out, expected_err='',
211 226 options=None):
212 227 """Utility to call 'ipython filename' and validate output/error.
213 228
214 229 This function raises an AssertionError if the validation fails.
215 230
216 231 Note that this starts IPython in a subprocess!
217 232
218 233 Parameters
219 234 ----------
220 235 fname : str
221 236 Name of the file to be executed (should have .py or .ipy extension).
222 237
223 238 expected_out : str
224 239 Expected stdout of the process.
225 240
226 241 expected_err : optional, str
227 242 Expected stderr of the process.
228 243
229 244 options : optional, list
230 245 Extra command-line flags to be passed to IPython.
231 246
232 247 Returns
233 248 -------
234 249 None
235 250 """
236 251
237 252 import nose.tools as nt
238 253
239 254 out, err = ipexec(fname, options)
240 255 #print 'OUT', out # dbg
241 256 #print 'ERR', err # dbg
242 257 # If there are any errors, we must check those befor stdout, as they may be
243 258 # more informative than simply having an empty stdout.
244 259 if err:
245 260 if expected_err:
246 261 nt.assert_equal("\n".join(err.strip().splitlines()), "\n".join(expected_err.strip().splitlines()))
247 262 else:
248 263 raise ValueError('Running file %r produced error: %r' %
249 264 (fname, err))
250 265 # If no errors or output on stderr was expected, match stdout
251 266 nt.assert_equal("\n".join(out.strip().splitlines()), "\n".join(expected_out.strip().splitlines()))
252 267
253 268
254 269 class TempFileMixin(object):
255 270 """Utility class to create temporary Python/IPython files.
256 271
257 272 Meant as a mixin class for test cases."""
258 273
259 274 def mktmp(self, src, ext='.py'):
260 275 """Make a valid python temp file."""
261 276 fname, f = temp_pyfile(src, ext)
262 277 self.tmpfile = f
263 278 self.fname = fname
264 279
265 280 def tearDown(self):
266 281 if hasattr(self, 'tmpfile'):
267 282 # If the tmpfile wasn't made because of skipped tests, like in
268 283 # win32, there's nothing to cleanup.
269 284 self.tmpfile.close()
270 285 try:
271 286 os.unlink(self.fname)
272 287 except:
273 288 # On Windows, even though we close the file, we still can't
274 289 # delete it. I have no clue why
275 290 pass
276 291
277 292 pair_fail_msg = ("Testing {0}\n\n"
278 293 "In:\n"
279 294 " {1!r}\n"
280 295 "Expected:\n"
281 296 " {2!r}\n"
282 297 "Got:\n"
283 298 " {3!r}\n")
284 299 def check_pairs(func, pairs):
285 300 """Utility function for the common case of checking a function with a
286 301 sequence of input/output pairs.
287 302
288 303 Parameters
289 304 ----------
290 305 func : callable
291 306 The function to be tested. Should accept a single argument.
292 307 pairs : iterable
293 308 A list of (input, expected_output) tuples.
294 309
295 310 Returns
296 311 -------
297 312 None. Raises an AssertionError if any output does not match the expected
298 313 value.
299 314 """
300 315 name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>"))
301 316 for inp, expected in pairs:
302 317 out = func(inp)
303 318 assert out == expected, pair_fail_msg.format(name, inp, expected, out)
304 319
305 320
306 321 if py3compat.PY3:
307 322 MyStringIO = StringIO
308 323 else:
309 324 # In Python 2, stdout/stderr can have either bytes or unicode written to them,
310 325 # so we need a class that can handle both.
311 326 class MyStringIO(StringIO):
312 327 def write(self, s):
313 328 s = py3compat.cast_unicode(s, encoding=DEFAULT_ENCODING)
314 329 super(MyStringIO, self).write(s)
315 330
316 331 notprinted_msg = """Did not find {0!r} in printed output (on {1}):
317 332 -------
318 333 {2!s}
319 334 -------
320 335 """
321 336
322 337 class AssertPrints(object):
323 338 """Context manager for testing that code prints certain text.
324 339
325 340 Examples
326 341 --------
327 342 >>> with AssertPrints("abc", suppress=False):
328 343 ... print "abcd"
329 344 ... print "def"
330 345 ...
331 346 abcd
332 347 def
333 348 """
334 349 def __init__(self, s, channel='stdout', suppress=True):
335 350 self.s = s
336 351 self.channel = channel
337 352 self.suppress = suppress
338 353
339 354 def __enter__(self):
340 355 self.orig_stream = getattr(sys, self.channel)
341 356 self.buffer = MyStringIO()
342 357 self.tee = Tee(self.buffer, channel=self.channel)
343 358 setattr(sys, self.channel, self.buffer if self.suppress else self.tee)
344 359
345 360 def __exit__(self, etype, value, traceback):
346 361 self.tee.flush()
347 362 setattr(sys, self.channel, self.orig_stream)
348 363 printed = self.buffer.getvalue()
349 364 assert self.s in printed, notprinted_msg.format(self.s, self.channel, printed)
350 365 return False
351 366
352 367 printed_msg = """Found {0!r} in printed output (on {1}):
353 368 -------
354 369 {2!s}
355 370 -------
356 371 """
357 372
358 373 class AssertNotPrints(AssertPrints):
359 374 """Context manager for checking that certain output *isn't* produced.
360 375
361 376 Counterpart of AssertPrints"""
362 377 def __exit__(self, etype, value, traceback):
363 378 self.tee.flush()
364 379 setattr(sys, self.channel, self.orig_stream)
365 380 printed = self.buffer.getvalue()
366 381 assert self.s not in printed, printed_msg.format(self.s, self.channel, printed)
367 382 return False
368 383
369 384 @contextmanager
370 385 def mute_warn():
371 386 from IPython.utils import warn
372 387 save_warn = warn.warn
373 388 warn.warn = lambda *a, **kw: None
374 389 try:
375 390 yield
376 391 finally:
377 392 warn.warn = save_warn
378 393
379 394 @contextmanager
380 395 def make_tempfile(name):
381 396 """ Create an empty, named, temporary file for the duration of the context.
382 397 """
383 398 f = open(name, 'w')
384 399 f.close()
385 400 try:
386 401 yield
387 402 finally:
388 403 os.unlink(name)
389 404
390 405
391 406 @contextmanager
392 407 def monkeypatch(obj, name, attr):
393 408 """
394 409 Context manager to replace attribute named `name` in `obj` with `attr`.
395 410 """
396 411 orig = getattr(obj, name)
397 412 setattr(obj, name, attr)
398 413 yield
399 414 setattr(obj, name, orig)
@@ -1,196 +1,214 b''
1 1 """Common utilities for the various process_* implementations.
2 2
3 3 This file is only meant to be imported by the platform-specific implementations
4 4 of subprocess utilities, and it contains tools that are common to all of them.
5 5 """
6 6
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2010-2011 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17 import subprocess
18 18 import shlex
19 19 import sys
20 20
21 21 from IPython.utils import py3compat
22 22
23 23 #-----------------------------------------------------------------------------
24 24 # Function definitions
25 25 #-----------------------------------------------------------------------------
26 26
27 27 def read_no_interrupt(p):
28 28 """Read from a pipe ignoring EINTR errors.
29 29
30 30 This is necessary because when reading from pipes with GUI event loops
31 31 running in the background, often interrupts are raised that stop the
32 32 command from completing."""
33 33 import errno
34 34
35 35 try:
36 36 return p.read()
37 37 except IOError as err:
38 38 if err.errno != errno.EINTR:
39 39 raise
40 40
41 41
42 42 def process_handler(cmd, callback, stderr=subprocess.PIPE):
43 43 """Open a command in a shell subprocess and execute a callback.
44 44
45 45 This function provides common scaffolding for creating subprocess.Popen()
46 46 calls. It creates a Popen object and then calls the callback with it.
47 47
48 48 Parameters
49 49 ----------
50 50 cmd : str
51 51 A string to be executed with the underlying system shell (by calling
52 52 :func:`Popen` with ``shell=True``.
53 53
54 54 callback : callable
55 55 A one-argument function that will be called with the Popen object.
56 56
57 57 stderr : file descriptor number, optional
58 58 By default this is set to ``subprocess.PIPE``, but you can also pass the
59 59 value ``subprocess.STDOUT`` to force the subprocess' stderr to go into
60 60 the same file descriptor as its stdout. This is useful to read stdout
61 61 and stderr combined in the order they are generated.
62 62
63 63 Returns
64 64 -------
65 65 The return value of the provided callback is returned.
66 66 """
67 67 sys.stdout.flush()
68 68 sys.stderr.flush()
69 69 # On win32, close_fds can't be true when using pipes for stdin/out/err
70 70 close_fds = sys.platform != 'win32'
71 71 p = subprocess.Popen(cmd, shell=True,
72 72 stdin=subprocess.PIPE,
73 73 stdout=subprocess.PIPE,
74 74 stderr=stderr,
75 75 close_fds=close_fds)
76 76
77 77 try:
78 78 out = callback(p)
79 79 except KeyboardInterrupt:
80 80 print('^C')
81 81 sys.stdout.flush()
82 82 sys.stderr.flush()
83 83 out = None
84 84 finally:
85 85 # Make really sure that we don't leave processes behind, in case the
86 86 # call above raises an exception
87 87 # We start by assuming the subprocess finished (to avoid NameErrors
88 88 # later depending on the path taken)
89 89 if p.returncode is None:
90 90 try:
91 91 p.terminate()
92 92 p.poll()
93 93 except OSError:
94 94 pass
95 95 # One last try on our way out
96 96 if p.returncode is None:
97 97 try:
98 98 p.kill()
99 99 except OSError:
100 100 pass
101 101
102 102 return out
103 103
104 104
105 105 def getoutput(cmd):
106 106 """Run a command and return its stdout/stderr as a string.
107 107
108 108 Parameters
109 109 ----------
110 110 cmd : str
111 111 A command to be executed in the system shell.
112 112
113 113 Returns
114 114 -------
115 115 output : str
116 116 A string containing the combination of stdout and stderr from the
117 117 subprocess, in whatever order the subprocess originally wrote to its
118 118 file descriptors (so the order of the information in this string is the
119 119 correct order as would be seen if running the command in a terminal).
120 120 """
121 121 out = process_handler(cmd, lambda p: p.communicate()[0], subprocess.STDOUT)
122 122 if out is None:
123 123 return ''
124 124 return py3compat.bytes_to_str(out)
125 125
126 126
127 127 def getoutputerror(cmd):
128 128 """Return (standard output, standard error) of executing cmd in a shell.
129 129
130 130 Accepts the same arguments as os.system().
131 131
132 132 Parameters
133 133 ----------
134 134 cmd : str
135 135 A command to be executed in the system shell.
136 136
137 137 Returns
138 138 -------
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.
152 170
153 171 This is a modified version of the standard library's shlex.split()
154 172 function, but with a default of posix=False for splitting, so that quotes
155 173 in inputs are respected.
156 174
157 175 if strict=False, then any errors shlex.split would raise will result in the
158 176 unparsed remainder being the last element of the list, rather than raising.
159 177 This is because we sometimes use arg_split to parse things other than
160 178 command-line args.
161 179 """
162 180
163 181 # Unfortunately, python's shlex module is buggy with unicode input:
164 182 # http://bugs.python.org/issue1170
165 183 # At least encoding the input when it's unicode seems to help, but there
166 184 # may be more problems lurking. Apparently this is fixed in python3.
167 185 is_unicode = False
168 186 if (not py3compat.PY3) and isinstance(s, unicode):
169 187 is_unicode = True
170 188 s = s.encode('utf-8')
171 189 lex = shlex.shlex(s, posix=posix)
172 190 lex.whitespace_split = True
173 191 # Extract tokens, ensuring that things like leaving open quotes
174 192 # does not cause this to raise. This is important, because we
175 193 # sometimes pass Python source through this (e.g. %timeit f(" ")),
176 194 # and it shouldn't raise an exception.
177 195 # It may be a bad idea to parse things that are not command-line args
178 196 # through this function, but we do, so let's be safe about it.
179 197 lex.commenters='' #fix for GH-1269
180 198 tokens = []
181 199 while True:
182 200 try:
183 201 tokens.append(next(lex))
184 202 except StopIteration:
185 203 break
186 204 except ValueError:
187 205 if strict:
188 206 raise
189 207 # couldn't parse, get remaining blob as last token
190 208 tokens.append(lex.token)
191 209 break
192 210
193 211 if is_unicode:
194 212 # Convert the tokens back to unicode.
195 213 tokens = [x.decode('utf-8') for x in tokens]
196 214 return tokens
@@ -1,122 +1,122 b''
1 1 # encoding: utf-8
2 2 """
3 3 Utilities for working with external processes.
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2008-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from __future__ import print_function
17 17
18 18 # Stdlib
19 19 import os
20 20 import sys
21 21 import shlex
22 22
23 23 # Our own
24 24 if sys.platform == 'win32':
25 25 from ._process_win32 import _find_cmd, system, getoutput, AvoidUNCPath, arg_split
26 26 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
34 34 #-----------------------------------------------------------------------------
35 35
36 36
37 37 class FindCmdError(Exception):
38 38 pass
39 39
40 40
41 41 def find_cmd(cmd):
42 42 """Find absolute path to executable cmd in a cross platform manner.
43 43
44 44 This function tries to determine the full path to a command line program
45 45 using `which` on Unix/Linux/OS X and `win32api` on Windows. Most of the
46 46 time it will use the version that is first on the users `PATH`.
47 47
48 48 Warning, don't use this to find IPython command line programs as there
49 49 is a risk you will find the wrong one. Instead find those using the
50 50 following code and looking for the application itself::
51 51
52 52 from IPython.utils.path import get_ipython_module_path
53 53 from IPython.utils.process import pycmd2argv
54 54 argv = pycmd2argv(get_ipython_module_path('IPython.terminal.ipapp'))
55 55
56 56 Parameters
57 57 ----------
58 58 cmd : str
59 59 The command line program to look for.
60 60 """
61 61 try:
62 62 path = _find_cmd(cmd).rstrip()
63 63 except OSError:
64 64 raise FindCmdError('command could not be found: %s' % cmd)
65 65 # which returns empty if not found
66 66 if path == '':
67 67 raise FindCmdError('command could not be found: %s' % cmd)
68 68 return os.path.abspath(path)
69 69
70 70
71 71 def is_cmd_found(cmd):
72 72 """Check whether executable `cmd` exists or not and return a bool."""
73 73 try:
74 74 find_cmd(cmd)
75 75 return True
76 76 except FindCmdError:
77 77 return False
78 78
79 79
80 80 def pycmd2argv(cmd):
81 81 r"""Take the path of a python command and return a list (argv-style).
82 82
83 83 This only works on Python based command line programs and will find the
84 84 location of the ``python`` executable using ``sys.executable`` to make
85 85 sure the right version is used.
86 86
87 87 For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe,
88 88 .com or .bat, and [, cmd] otherwise.
89 89
90 90 Parameters
91 91 ----------
92 92 cmd : string
93 93 The path of the command.
94 94
95 95 Returns
96 96 -------
97 97 argv-style list.
98 98 """
99 99 ext = os.path.splitext(cmd)[1]
100 100 if ext in ['.exe', '.com', '.bat']:
101 101 return [cmd]
102 102 else:
103 103 return [sys.executable, cmd]
104 104
105 105
106 106 def abbrev_cwd():
107 107 """ Return abbreviated version of cwd, e.g. d:mydir """
108 108 cwd = os.getcwdu().replace('\\','/')
109 109 drivepart = ''
110 110 tail = cwd
111 111 if sys.platform == 'win32':
112 112 if len(cwd) < 4:
113 113 return cwd
114 114 drivepart,tail = os.path.splitdrive(cwd)
115 115
116 116
117 117 parts = tail.split('/')
118 118 if len(parts) > 2:
119 119 tail = '/'.join(parts[-2:])
120 120
121 121 return (drivepart + (
122 122 cwd == '/' and '/' or tail))
@@ -1,106 +1,132 b''
1 1 """TemporaryDirectory class, copied from Python 3.2.
2 2
3 3 This is copied from the stdlib and will be standard in Python 3.2 and onwards.
4 4 """
5 5
6 6 import os as _os
7 7
8 8 # This code should only be used in Python versions < 3.2, since after that we
9 9 # can rely on the stdlib itself.
10 10 try:
11 11 from tempfile import TemporaryDirectory
12 12
13 13 except ImportError:
14 14 from tempfile import mkdtemp, template
15 15
16 16 class TemporaryDirectory(object):
17 17 """Create and return a temporary directory. This has the same
18 18 behavior as mkdtemp but can be used as a context manager. For
19 19 example:
20 20
21 21 with TemporaryDirectory() as tmpdir:
22 22 ...
23 23
24 24 Upon exiting the context, the directory and everthing contained
25 25 in it are removed.
26 26 """
27 27
28 28 def __init__(self, suffix="", prefix=template, dir=None):
29 29 self.name = mkdtemp(suffix, prefix, dir)
30 30 self._closed = False
31 31
32 32 def __enter__(self):
33 33 return self.name
34 34
35 35 def cleanup(self):
36 36 if not self._closed:
37 37 self._rmtree(self.name)
38 38 self._closed = True
39 39
40 40 def __exit__(self, exc, value, tb):
41 41 self.cleanup()
42 42
43 43 __del__ = cleanup
44 44
45 45
46 46 # XXX (ncoghlan): The following code attempts to make
47 47 # this class tolerant of the module nulling out process
48 48 # that happens during CPython interpreter shutdown
49 49 # Alas, it doesn't actually manage it. See issue #10188
50 50 _listdir = staticmethod(_os.listdir)
51 51 _path_join = staticmethod(_os.path.join)
52 52 _isdir = staticmethod(_os.path.isdir)
53 53 _remove = staticmethod(_os.remove)
54 54 _rmdir = staticmethod(_os.rmdir)
55 55 _os_error = _os.error
56 56
57 57 def _rmtree(self, path):
58 58 # Essentially a stripped down version of shutil.rmtree. We can't
59 59 # use globals because they may be None'ed out at shutdown.
60 60 for name in self._listdir(path):
61 61 fullname = self._path_join(path, name)
62 62 try:
63 63 isdir = self._isdir(fullname)
64 64 except self._os_error:
65 65 isdir = False
66 66 if isdir:
67 67 self._rmtree(fullname)
68 68 else:
69 69 try:
70 70 self._remove(fullname)
71 71 except self._os_error:
72 72 pass
73 73 try:
74 74 self._rmdir(path)
75 75 except self._os_error:
76 76 pass
77 77
78 78
79 79 class NamedFileInTemporaryDirectory(object):
80 80
81 81 def __init__(self, filename, mode='w+b', bufsize=-1, **kwds):
82 82 """
83 83 Open a file named `filename` in a temporary directory.
84 84
85 85 This context manager is preferred over `NamedTemporaryFile` in
86 86 stdlib `tempfile` when one needs to reopen the file.
87 87
88 88 Arguments `mode` and `bufsize` are passed to `open`.
89 89 Rest of the arguments are passed to `TemporaryDirectory`.
90 90
91 91 """
92 92 self._tmpdir = TemporaryDirectory(**kwds)
93 93 path = _os.path.join(self._tmpdir.name, filename)
94 94 self.file = open(path, mode, bufsize)
95 95
96 96 def cleanup(self):
97 97 self.file.close()
98 98 self._tmpdir.cleanup()
99 99
100 100 __del__ = cleanup
101 101
102 102 def __enter__(self):
103 103 return self.file
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()
@@ -1,134 +1,146 b''
1 1 # encoding: utf-8
2 2 """
3 3 Tests for platutils.py
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2008-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16
17 17 import sys
18 18 import os
19 19 from unittest import TestCase
20 20
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
28 29 python = os.path.basename(sys.executable)
29 30
30 31 #-----------------------------------------------------------------------------
31 32 # Tests
32 33 #-----------------------------------------------------------------------------
33 34
34 35
35 36 @dec.skip_win32
36 37 def test_find_cmd_ls():
37 38 """Make sure we can find the full path to ls."""
38 39 path = find_cmd('ls')
39 40 nt.assert_true(path.endswith('ls'))
40 41
41 42
42 43 def has_pywin32():
43 44 try:
44 45 import win32api
45 46 except ImportError:
46 47 return False
47 48 return True
48 49
49 50
50 51 @dec.onlyif(has_pywin32, "This test requires win32api to run")
51 52 def test_find_cmd_pythonw():
52 53 """Try to find pythonw on Windows."""
53 54 path = find_cmd('pythonw')
54 55 nt.assert_true(path.endswith('pythonw.exe'))
55 56
56 57
57 58 @dec.onlyif(lambda : sys.platform != 'win32' or has_pywin32(),
58 59 "This test runs on posix or in win32 with win32api installed")
59 60 def test_find_cmd_fail():
60 61 """Make sure that FindCmdError is raised if we can't find the cmd."""
61 62 nt.assert_raises(FindCmdError,find_cmd,'asdfasdf')
62 63
63 64
64 65 @dec.skip_win32
65 66 def test_arg_split():
66 67 """Ensure that argument lines are correctly split like in a shell."""
67 68 tests = [['hi', ['hi']],
68 69 [u'hi', [u'hi']],
69 70 ['hello there', ['hello', 'there']],
70 71 # \u01ce == \N{LATIN SMALL LETTER A WITH CARON}
71 72 # Do not use \N because the tests crash with syntax error in
72 73 # some cases, for example windows python2.6.
73 74 [u'h\u01cello', [u'h\u01cello']],
74 75 ['something "with quotes"', ['something', '"with quotes"']],
75 76 ]
76 77 for argstr, argv in tests:
77 78 nt.assert_equal(arg_split(argstr), argv)
78 79
79 80 @dec.skip_if_not_win32
80 81 def test_arg_split_win32():
81 82 """Ensure that argument lines are correctly split like in a shell."""
82 83 tests = [['hi', ['hi']],
83 84 [u'hi', [u'hi']],
84 85 ['hello there', ['hello', 'there']],
85 86 [u'h\u01cello', [u'h\u01cello']],
86 87 ['something "with quotes"', ['something', 'with quotes']],
87 88 ]
88 89 for argstr, argv in tests:
89 90 nt.assert_equal(arg_split(argstr), argv)
90 91
91 92
92 93 class SubProcessTestCase(TestCase, tt.TempFileMixin):
93 94 def setUp(self):
94 95 """Make a valid python temp file."""
95 96 lines = ["from __future__ import print_function",
96 97 "import sys",
97 98 "print('on stdout', end='', file=sys.stdout)",
98 99 "print('on stderr', end='', file=sys.stderr)",
99 100 "sys.stdout.flush()",
100 101 "sys.stderr.flush()"]
101 102 self.mktmp('\n'.join(lines))
102 103
103 104 def test_system(self):
104 105 status = system('%s "%s"' % (python, self.fname))
105 106 self.assertEqual(status, 0)
106 107
107 108 def test_system_quotes(self):
108 109 status = system('%s -c "import sys"' % python)
109 110 self.assertEqual(status, 0)
110 111
111 112 def test_getoutput(self):
112 113 out = getoutput('%s "%s"' % (python, self.fname))
113 114 # we can't rely on the order the line buffered streams are flushed
114 115 try:
115 116 self.assertEqual(out, 'on stderron stdout')
116 117 except AssertionError:
117 118 self.assertEqual(out, 'on stdouton stderr')
118 119
119 120 def test_getoutput_quoted(self):
120 121 out = getoutput('%s -c "print (1)"' % python)
121 122 self.assertEqual(out.strip(), '1')
122 123
123 124 #Invalid quoting on windows
124 125 @dec.skip_win32
125 126 def test_getoutput_quoted2(self):
126 127 out = getoutput("%s -c 'print (1)'" % python)
127 128 self.assertEqual(out.strip(), '1')
128 129 out = getoutput("%s -c 'print (\"1\")'" % python)
129 130 self.assertEqual(out.strip(), '1')
130 131
131 132 def test_getoutput_error(self):
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)
@@ -1,20 +1,28 b''
1 1 #-----------------------------------------------------------------------------
2 2 # Copyright (C) 2012- The IPython Development Team
3 3 #
4 4 # Distributed under the terms of the BSD License. The full license is in
5 5 # the file COPYING, distributed as part of this software.
6 6 #-----------------------------------------------------------------------------
7 7
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():
14 15 with NamedFileInTemporaryDirectory('filename') as file:
15 16 name = file.name
16 17 assert not file.closed
17 18 assert os.path.exists(name)
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