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 |
|
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 useful |
|
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 |
|
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 |
|
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 |
|
|
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() |
|
58 | with self.create_temp_cwd(): | |
66 | self.copy_files_to(['notebook*.ipynb'], 'subdir/') |
|
59 | self.copy_files_to(['notebook*.ipynb'], 'subdir/') | |
67 |
|
|
60 | self.call('nbconvert --to="python" --notebooks=' | |
68 |
' |
|
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 |
|
|
71 | self.call('nbconvert --to="python" --notebooks=' | |
79 |
' |
|
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 |
|
|
84 | self.call('nbconvert --to="latex" notebook1' | |
91 |
' |
|
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 |
|
|
97 | self.call('nbconvert --to=slides --notebooks=' | |
103 |
' |
|
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 |
|
|
109 | self.call('nbconvert --to="python" --notebooks=' | |
115 |
' |
|
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 |
|
|
120 | self.call('nbconvert --to="python" --notebooks=' | |
126 |
' |
|
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 |
|
|
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', |
|
140 | with self.create_temp_cwd(['notebook*.ipynb', | |
|
141 | 'ipython_nbconvert_config.py', | |||
146 | 'override.py']): |
|
142 | 'override.py']): | |
147 |
|
|
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