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