##// END OF EJS Templates
Simplify implementation of TemporaryWorkingDirectory....
Thomas Kluyver -
Show More
@@ -1,164 +1,164 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 io
16 import io
17 import os
17 import os
18 import glob
18 import glob
19 import shutil
19 import shutil
20 import unittest
20 import unittest
21
21
22 import IPython
22 import IPython
23 from IPython.nbformat import current
23 from IPython.nbformat import current
24 from IPython.utils.tempdir import TemporaryWorkingDirectory
24 from IPython.utils.tempdir import TemporaryWorkingDirectory
25 from IPython.utils.path import get_ipython_package_dir
25 from IPython.utils.path import get_ipython_package_dir
26 from IPython.utils.process import get_output_error_code
26 from IPython.utils.process import get_output_error_code
27 from IPython.testing.tools import get_ipython_cmd
27 from IPython.testing.tools import get_ipython_cmd
28
28
29 # a trailing space allows for simpler concatenation with the other arguments
29 # a trailing space allows for simpler concatenation with the other arguments
30 ipy_cmd = get_ipython_cmd(as_string=True) + " "
30 ipy_cmd = get_ipython_cmd(as_string=True) + " "
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Classes and functions
33 # Classes and functions
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36
36
37 class TestsBase(unittest.TestCase):
37 class TestsBase(unittest.TestCase):
38 """Base tests class. Contains useful fuzzy comparison and nbconvert
38 """Base tests class. Contains useful fuzzy comparison and nbconvert
39 functions."""
39 functions."""
40
40
41
41
42 def fuzzy_compare(self, a, b, newlines_are_spaces=True, tabs_are_spaces=True,
42 def fuzzy_compare(self, a, b, newlines_are_spaces=True, tabs_are_spaces=True,
43 fuzzy_spacing=True, ignore_spaces=False,
43 fuzzy_spacing=True, ignore_spaces=False,
44 ignore_newlines=False, case_sensitive=False, leave_padding=False):
44 ignore_newlines=False, case_sensitive=False, leave_padding=False):
45 """
45 """
46 Performs a fuzzy comparison of two strings. A fuzzy comparison is a
46 Performs a fuzzy comparison of two strings. A fuzzy comparison is a
47 comparison that ignores insignificant differences in the two comparands.
47 comparison that ignores insignificant differences in the two comparands.
48 The significance of certain differences can be specified via the keyword
48 The significance of certain differences can be specified via the keyword
49 parameters of this method.
49 parameters of this method.
50 """
50 """
51
51
52 if not leave_padding:
52 if not leave_padding:
53 a = a.strip()
53 a = a.strip()
54 b = b.strip()
54 b = b.strip()
55
55
56 if ignore_newlines:
56 if ignore_newlines:
57 a = a.replace('\n', '')
57 a = a.replace('\n', '')
58 b = b.replace('\n', '')
58 b = b.replace('\n', '')
59
59
60 if newlines_are_spaces:
60 if newlines_are_spaces:
61 a = a.replace('\n', ' ')
61 a = a.replace('\n', ' ')
62 b = b.replace('\n', ' ')
62 b = b.replace('\n', ' ')
63
63
64 if tabs_are_spaces:
64 if tabs_are_spaces:
65 a = a.replace('\t', ' ')
65 a = a.replace('\t', ' ')
66 b = b.replace('\t', ' ')
66 b = b.replace('\t', ' ')
67
67
68 if ignore_spaces:
68 if ignore_spaces:
69 a = a.replace(' ', '')
69 a = a.replace(' ', '')
70 b = b.replace(' ', '')
70 b = b.replace(' ', '')
71
71
72 if fuzzy_spacing:
72 if fuzzy_spacing:
73 a = self.recursive_replace(a, ' ', ' ')
73 a = self.recursive_replace(a, ' ', ' ')
74 b = self.recursive_replace(b, ' ', ' ')
74 b = self.recursive_replace(b, ' ', ' ')
75
75
76 if not case_sensitive:
76 if not case_sensitive:
77 a = a.lower()
77 a = a.lower()
78 b = b.lower()
78 b = b.lower()
79
79
80 self.assertEqual(a, b)
80 self.assertEqual(a, b)
81
81
82
82
83 def recursive_replace(self, text, search, replacement):
83 def recursive_replace(self, text, search, replacement):
84 """
84 """
85 Performs a recursive replacement operation. Replaces all instances
85 Performs a recursive replacement operation. Replaces all instances
86 of a search string in a text string with a replacement string until
86 of a search string in a text string with a replacement string until
87 the search string no longer exists. Recursion is needed because the
87 the search string no longer exists. Recursion is needed because the
88 replacement string may generate additional search strings.
88 replacement string may generate additional search strings.
89
89
90 For example:
90 For example:
91 Replace "ii" with "i" in the string "Hiiii" yields "Hii"
91 Replace "ii" with "i" in the string "Hiiii" yields "Hii"
92 Another replacement cds "Hi" (the desired output)
92 Another replacement cds "Hi" (the desired output)
93
93
94 Parameters
94 Parameters
95 ----------
95 ----------
96 text : string
96 text : string
97 Text to replace in.
97 Text to replace in.
98 search : string
98 search : string
99 String to search for within "text"
99 String to search for within "text"
100 replacement : string
100 replacement : string
101 String to replace "search" with
101 String to replace "search" with
102 """
102 """
103 while search in text:
103 while search in text:
104 text = text.replace(search, replacement)
104 text = text.replace(search, replacement)
105 return text
105 return text
106
106
107 def create_temp_cwd(self, copy_filenames=None):
107 def create_temp_cwd(self, copy_filenames=None):
108 temp_dir = TemporaryWorkingDirectory()
108 temp_dir = TemporaryWorkingDirectory()
109
109
110 #Copy the files if requested.
110 #Copy the files if requested.
111 if copy_filenames is not None:
111 if copy_filenames is not None:
112 self.copy_files_to(copy_filenames)
112 self.copy_files_to(copy_filenames, dest=temp_dir.name)
113
113
114 #Return directory handler
114 #Return directory handler
115 return temp_dir
115 return temp_dir
116
116
117 def create_empty_notebook(self, path):
117 def create_empty_notebook(self, path):
118 nb = current.new_notebook()
118 nb = current.new_notebook()
119 nb.worksheets.append(current.new_worksheet())
119 nb.worksheets.append(current.new_worksheet())
120 with io.open(path, 'w', encoding='utf-8') as f:
120 with io.open(path, 'w', encoding='utf-8') as f:
121 current.write(nb, f, 'json')
121 current.write(nb, f, 'json')
122
122
123
123
124 def copy_files_to(self, copy_filenames, dest='.'):
124 def copy_files_to(self, copy_filenames, dest='.'):
125 "Copy test files into the destination directory"
125 "Copy test files into the destination directory"
126 if not os.path.isdir(dest):
126 if not os.path.isdir(dest):
127 os.makedirs(dest)
127 os.makedirs(dest)
128 files_path = self._get_files_path()
128 files_path = self._get_files_path()
129 for pattern in copy_filenames:
129 for pattern in copy_filenames:
130 for match in glob.glob(os.path.join(files_path, pattern)):
130 for match in glob.glob(os.path.join(files_path, pattern)):
131 shutil.copyfile(match, os.path.join(dest, os.path.basename(match)))
131 shutil.copyfile(match, os.path.join(dest, os.path.basename(match)))
132
132
133
133
134 def _get_files_path(self):
134 def _get_files_path(self):
135
135
136 #Get the relative path to this module in the IPython directory.
136 #Get the relative path to this module in the IPython directory.
137 names = self.__module__.split('.')[1:-1]
137 names = self.__module__.split('.')[1:-1]
138 names.append('files')
138 names.append('files')
139
139
140 #Build a path using the IPython directory and the relative path we just
140 #Build a path using the IPython directory and the relative path we just
141 #found.
141 #found.
142 path = get_ipython_package_dir()
142 path = get_ipython_package_dir()
143 for name in names:
143 for name in names:
144 path = os.path.join(path, name)
144 path = os.path.join(path, name)
145 return path
145 return path
146
146
147
147
148 def call(self, parameters, ignore_return_code=False):
148 def call(self, parameters, ignore_return_code=False):
149 """
149 """
150 Execute a, IPython shell command, listening for both Errors and non-zero
150 Execute a, IPython shell command, listening for both Errors and non-zero
151 return codes.
151 return codes.
152
152
153 Parameters
153 Parameters
154 ----------
154 ----------
155 parameters : str
155 parameters : str
156 List of parameters to pass to IPython.
156 List of parameters to pass to IPython.
157 ignore_return_code : optional bool (default False)
157 ignore_return_code : optional bool (default False)
158 Throw an OSError if the return code
158 Throw an OSError if the return code
159 """
159 """
160
160
161 stdout, stderr, retcode = get_output_error_code(ipy_cmd + parameters)
161 stdout, stderr, retcode = get_output_error_code(ipy_cmd + parameters)
162 if not (retcode == 0 or ignore_return_code):
162 if not (retcode == 0 or ignore_return_code):
163 raise OSError(stderr)
163 raise OSError(stderr)
164 return stdout, stderr
164 return stdout, stderr
@@ -1,151 +1,145 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 from __future__ import print_function
5 from __future__ import print_function
6
6
7 import os as _os
7 import os as _os
8 import warnings as _warnings
8 import warnings as _warnings
9 import sys as _sys
9 import sys as _sys
10
10
11 # This code should only be used in Python versions < 3.2, since after that we
11 # This code should only be used in Python versions < 3.2, since after that we
12 # can rely on the stdlib itself.
12 # can rely on the stdlib itself.
13 try:
13 try:
14 from tempfile import TemporaryDirectory
14 from tempfile import TemporaryDirectory
15
15
16 except ImportError:
16 except ImportError:
17 from tempfile import mkdtemp, template
17 from tempfile import mkdtemp, template
18
18
19 class TemporaryDirectory(object):
19 class TemporaryDirectory(object):
20 """Create and return a temporary directory. This has the same
20 """Create and return a temporary directory. This has the same
21 behavior as mkdtemp but can be used as a context manager. For
21 behavior as mkdtemp but can be used as a context manager. For
22 example:
22 example:
23
23
24 with TemporaryDirectory() as tmpdir:
24 with TemporaryDirectory() as tmpdir:
25 ...
25 ...
26
26
27 Upon exiting the context, the directory and everthing contained
27 Upon exiting the context, the directory and everthing contained
28 in it are removed.
28 in it are removed.
29 """
29 """
30
30
31 def __init__(self, suffix="", prefix=template, dir=None):
31 def __init__(self, suffix="", prefix=template, dir=None):
32 self.name = mkdtemp(suffix, prefix, dir)
32 self.name = mkdtemp(suffix, prefix, dir)
33 self._closed = False
33 self._closed = False
34
34
35 def __enter__(self):
35 def __enter__(self):
36 return self.name
36 return self.name
37
37
38 def cleanup(self, _warn=False):
38 def cleanup(self, _warn=False):
39 if self.name and not self._closed:
39 if self.name and not self._closed:
40 try:
40 try:
41 self._rmtree(self.name)
41 self._rmtree(self.name)
42 except (TypeError, AttributeError) as ex:
42 except (TypeError, AttributeError) as ex:
43 # Issue #10188: Emit a warning on stderr
43 # Issue #10188: Emit a warning on stderr
44 # if the directory could not be cleaned
44 # if the directory could not be cleaned
45 # up due to missing globals
45 # up due to missing globals
46 if "None" not in str(ex):
46 if "None" not in str(ex):
47 raise
47 raise
48 print("ERROR: {!r} while cleaning up {!r}".format(ex, self,),
48 print("ERROR: {!r} while cleaning up {!r}".format(ex, self,),
49 file=_sys.stderr)
49 file=_sys.stderr)
50 return
50 return
51 self._closed = True
51 self._closed = True
52 if _warn:
52 if _warn:
53 self._warn("Implicitly cleaning up {!r}".format(self),
53 self._warn("Implicitly cleaning up {!r}".format(self),
54 Warning)
54 Warning)
55
55
56 def __exit__(self, exc, value, tb):
56 def __exit__(self, exc, value, tb):
57 self.cleanup()
57 self.cleanup()
58
58
59 def __del__(self):
59 def __del__(self):
60 # Issue a ResourceWarning if implicit cleanup needed
60 # Issue a ResourceWarning if implicit cleanup needed
61 self.cleanup(_warn=True)
61 self.cleanup(_warn=True)
62
62
63
63
64 # XXX (ncoghlan): The following code attempts to make
64 # XXX (ncoghlan): The following code attempts to make
65 # this class tolerant of the module nulling out process
65 # this class tolerant of the module nulling out process
66 # that happens during CPython interpreter shutdown
66 # that happens during CPython interpreter shutdown
67 # Alas, it doesn't actually manage it. See issue #10188
67 # Alas, it doesn't actually manage it. See issue #10188
68 _listdir = staticmethod(_os.listdir)
68 _listdir = staticmethod(_os.listdir)
69 _path_join = staticmethod(_os.path.join)
69 _path_join = staticmethod(_os.path.join)
70 _isdir = staticmethod(_os.path.isdir)
70 _isdir = staticmethod(_os.path.isdir)
71 _remove = staticmethod(_os.remove)
71 _remove = staticmethod(_os.remove)
72 _rmdir = staticmethod(_os.rmdir)
72 _rmdir = staticmethod(_os.rmdir)
73 _os_error = _os.error
73 _os_error = _os.error
74 _warn = _warnings.warn
74 _warn = _warnings.warn
75
75
76 def _rmtree(self, path):
76 def _rmtree(self, path):
77 # Essentially a stripped down version of shutil.rmtree. We can't
77 # Essentially a stripped down version of shutil.rmtree. We can't
78 # use globals because they may be None'ed out at shutdown.
78 # use globals because they may be None'ed out at shutdown.
79 for name in self._listdir(path):
79 for name in self._listdir(path):
80 fullname = self._path_join(path, name)
80 fullname = self._path_join(path, name)
81 try:
81 try:
82 isdir = self._isdir(fullname)
82 isdir = self._isdir(fullname)
83 except self._os_error:
83 except self._os_error:
84 isdir = False
84 isdir = False
85 if isdir:
85 if isdir:
86 self._rmtree(fullname)
86 self._rmtree(fullname)
87 else:
87 else:
88 try:
88 try:
89 self._remove(fullname)
89 self._remove(fullname)
90 except self._os_error:
90 except self._os_error:
91 pass
91 pass
92 try:
92 try:
93 self._rmdir(path)
93 self._rmdir(path)
94 except self._os_error:
94 except self._os_error:
95 pass
95 pass
96
96
97
97
98 class NamedFileInTemporaryDirectory(object):
98 class NamedFileInTemporaryDirectory(object):
99
99
100 def __init__(self, filename, mode='w+b', bufsize=-1, **kwds):
100 def __init__(self, filename, mode='w+b', bufsize=-1, **kwds):
101 """
101 """
102 Open a file named `filename` in a temporary directory.
102 Open a file named `filename` in a temporary directory.
103
103
104 This context manager is preferred over `NamedTemporaryFile` in
104 This context manager is preferred over `NamedTemporaryFile` in
105 stdlib `tempfile` when one needs to reopen the file.
105 stdlib `tempfile` when one needs to reopen the file.
106
106
107 Arguments `mode` and `bufsize` are passed to `open`.
107 Arguments `mode` and `bufsize` are passed to `open`.
108 Rest of the arguments are passed to `TemporaryDirectory`.
108 Rest of the arguments are passed to `TemporaryDirectory`.
109
109
110 """
110 """
111 self._tmpdir = TemporaryDirectory(**kwds)
111 self._tmpdir = TemporaryDirectory(**kwds)
112 path = _os.path.join(self._tmpdir.name, filename)
112 path = _os.path.join(self._tmpdir.name, filename)
113 self.file = open(path, mode, bufsize)
113 self.file = open(path, mode, bufsize)
114
114
115 def cleanup(self):
115 def cleanup(self):
116 self.file.close()
116 self.file.close()
117 self._tmpdir.cleanup()
117 self._tmpdir.cleanup()
118
118
119 __del__ = cleanup
119 __del__ = cleanup
120
120
121 def __enter__(self):
121 def __enter__(self):
122 return self.file
122 return self.file
123
123
124 def __exit__(self, type, value, traceback):
124 def __exit__(self, type, value, traceback):
125 self.cleanup()
125 self.cleanup()
126
126
127
127
128 class TemporaryWorkingDirectory(TemporaryDirectory):
128 class TemporaryWorkingDirectory(TemporaryDirectory):
129 """
129 """
130 Creates a temporary directory and sets the cwd to that directory.
130 Creates a temporary directory and sets the cwd to that directory.
131 Automatically reverts to previous cwd upon cleanup.
131 Automatically reverts to previous cwd upon cleanup.
132 Usage example:
132 Usage example:
133
133
134 with TemporaryWorakingDirectory() as tmpdir:
134 with TemporaryWorkingDirectory() as tmpdir:
135 ...
135 ...
136 """
136 """
137
137 def __enter__(self):
138 def __init__(self, **kw):
139 super(TemporaryWorkingDirectory, self).__init__(**kw)
140
141 #Change cwd to new temp dir. Remember old cwd.
142 self.old_wd = _os.getcwd()
138 self.old_wd = _os.getcwd()
143 _os.chdir(self.name)
139 _os.chdir(self.name)
140 return super(TemporaryWorkingDirectory, self).__enter__()
144
141
145
142 def __exit__(self, exc, value, tb):
146 def cleanup(self, _warn=False):
147 #Revert to old cwd.
148 _os.chdir(self.old_wd)
143 _os.chdir(self.old_wd)
144 return super(TemporaryWorkingDirectory, self).__exit__(exc, value, tb)
149
145
150 #Cleanup
151 super(TemporaryWorkingDirectory, self).cleanup(_warn=_warn)
General Comments 0
You need to be logged in to leave comments. Login now