1# Copyright 2010 Google Inc. All Rights Reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Fake tempfile module. 16 17Fake implementation of the python2.4.1 tempfile built-in module that works with 18a FakeFilesystem object. 19""" 20#pylint: disable-all 21 22import errno 23import logging 24import os 25import stat 26import tempfile 27 28import fake_filesystem 29 30try: 31 import StringIO as io # pylint: disable-msg=C6204 32except ImportError: 33 import io # pylint: disable-msg=C6204 34 35 36class FakeTempfileModule(object): 37 """Uses a FakeFilesystem to provide a mock for the tempfile 2.4.1 module. 38 39 Common usage: 40 filesystem = fake_filesystem.FakeFilesystem() 41 my_tempfile_module = mock_tempfile.FakeTempfileModule(filesystem) 42 43 See also: default keyword arguments for Dependency Injection on 44 http://go/tott-episode-12 45 """ 46 47 def __init__(self, filesystem): 48 self._filesystem = filesystem 49 self._tempfile = tempfile 50 self.tempdir = None # initialized by mktemp(), others 51 self._temp_prefix = 'tmp' 52 self._mktemp_retvals = [] 53 54 # pylint: disable-msg=W0622 55 def _TempFilename(self, suffix='', prefix=None, dir=None): 56 """Create a temporary filename that does not exist. 57 58 This is a re-implementation of how tempfile creates random filenames, 59 and is probably different. 60 61 Does not modify self._filesystem, that's your job. 62 63 Output: self.tempdir is initialized if unset 64 Args: 65 suffix: filename suffix 66 prefix: filename prefix 67 dir: dir to put filename in 68 Returns: 69 string, temp filename that does not exist 70 """ 71 if dir is None: 72 dir = self._filesystem.JoinPaths(self._filesystem.root.name, 'tmp') 73 filename = None 74 if prefix is None: 75 prefix = self._temp_prefix 76 while not filename or self._filesystem.Exists(filename): 77 # pylint: disable-msg=W0212 78 filename = self._filesystem.JoinPaths(dir, '%s%s%s' % ( 79 prefix, 80 next(self._tempfile._RandomNameSequence()), 81 suffix)) 82 return filename 83 84 # pylint: disable-msg=W0622,W0613 85 def TemporaryFile(self, mode='w+b', bufsize=-1, 86 suffix='', prefix=None, dir=None): 87 """Return a file-like object deleted on close(). 88 89 Python 2.4.1 tempfile.TemporaryFile.__doc__ = 90 >Return a file (or file-like) object that can be used as a temporary 91 >storage area. The file is created using mkstemp. It will be destroyed as 92 >soon as it is closed (including an implicit close when the object is 93 >garbage collected). Under Unix, the directory entry for the file is 94 >removed immediately after the file is created. Other platforms do not 95 >support this; your code should not rely on a temporary file created using 96 >this function having or not having a visible name in the file system. 97 > 98 >The mode parameter defaults to 'w+b' so that the file created can be read 99 >and written without being closed. Binary mode is used so that it behaves 100 >consistently on all platforms without regard for the data that is stored. 101 >bufsize defaults to -1, meaning that the operating system default is used. 102 > 103 >The dir, prefix and suffix parameters are passed to mkstemp() 104 105 Args: 106 mode: optional string, see above 107 bufsize: optional int, see above 108 suffix: optional string, see above 109 prefix: optional string, see above 110 dir: optional string, see above 111 Returns: 112 a file-like object. 113 """ 114 # pylint: disable-msg=C6002 115 # TODO: prefix, suffix, bufsize, dir, mode unused? 116 # cannot be cStringIO due to .name requirement below 117 retval = io.StringIO() 118 retval.name = '<fdopen>' # as seen on 2.4.3 119 return retval 120 121 # pylint: disable-msg=W0622,W0613 122 def NamedTemporaryFile(self, mode='w+b', bufsize=-1, 123 suffix='', prefix=None, dir=None, delete=True): 124 """Return a file-like object with name that is deleted on close(). 125 126 Python 2.4.1 tempfile.NamedTemporaryFile.__doc__ = 127 >This function operates exactly as TemporaryFile() does, except that 128 >the file is guaranteed to have a visible name in the file system. That 129 >name can be retrieved from the name member of the file object. 130 131 Args: 132 mode: optional string, see above 133 bufsize: optional int, see above 134 suffix: optional string, see above 135 prefix: optional string, see above 136 dir: optional string, see above 137 delete: optional bool, see above 138 Returns: 139 a file-like object including obj.name 140 """ 141 # pylint: disable-msg=C6002 142 # TODO: bufsiz unused? 143 temp = self.mkstemp(suffix=suffix, prefix=prefix, dir=dir) 144 filename = temp[1] 145 mock_open = fake_filesystem.FakeFileOpen( 146 self._filesystem, delete_on_close=delete) 147 obj = mock_open(filename, mode) 148 obj.name = filename 149 return obj 150 151 # pylint: disable-msg=C6409 152 def mkstemp(self, suffix='', prefix=None, dir=None, text=False): 153 """Create temp file, returning a 2-tuple: (9999, filename). 154 155 Important: Returns 9999 instead of a real file descriptor! 156 157 Python 2.4.1 tempfile.mkstemp.__doc__ = 158 >mkstemp([suffix, [prefix, [dir, [text]]]]) 159 > 160 >User-callable function to create and return a unique temporary file. 161 >The return value is a pair (fd, name) where fd is the file descriptor 162 >returned by os.open, and name is the filename. 163 > 164 >...[snip args]... 165 > 166 >The file is readable and writable only by the creating user ID. 167 >If the operating system uses permission bits to indicate whether 168 >a file is executable, the file is executable by no one. The file 169 >descriptor is not inherited by children of this process. 170 > 171 >Caller is responsible for deleting the file when done with it. 172 173 NOTE: if dir is unspecified, this call creates a directory. 174 175 Output: self.tempdir is initialized if unset 176 Args: 177 suffix: optional string, filename suffix 178 prefix: optional string, filename prefix 179 dir: optional string, directory for temp file; must exist before call 180 text: optional boolean, True = open file in text mode. 181 default False = open file in binary mode. 182 Returns: 183 2-tuple containing 184 [0] = int, file descriptor number for the file object 185 [1] = string, absolute pathname of a file 186 Raises: 187 OSError: when dir= is specified but does not exist 188 """ 189 # pylint: disable-msg=C6002 190 # TODO: optional boolean text is unused? 191 # default dir affected by "global" 192 filename = self._TempEntryname(suffix, prefix, dir) 193 fh = self._filesystem.CreateFile(filename, st_mode=stat.S_IFREG|0o600) 194 fd = self._filesystem.AddOpenFile(fh) 195 196 self._mktemp_retvals.append(filename) 197 return (fd, filename) 198 199 # pylint: disable-msg=C6409 200 def mkdtemp(self, suffix='', prefix=None, dir=None): 201 """Create temp directory, returns string, absolute pathname. 202 203 Python 2.4.1 tempfile.mkdtemp.__doc__ = 204 >mkdtemp([suffix[, prefix[, dir]]]) 205 >Creates a temporary directory in the most secure manner 206 >possible. [...] 207 > 208 >The user of mkdtemp() is responsible for deleting the temporary 209 >directory and its contents when done with it. 210 > [...] 211 >mkdtemp() returns the absolute pathname of the new directory. [...] 212 213 Args: 214 suffix: optional string, filename suffix 215 prefix: optional string, filename prefix 216 dir: optional string, directory for temp dir. Must exist before call 217 Returns: 218 string, directory name 219 """ 220 dirname = self._TempEntryname(suffix, prefix, dir) 221 self._filesystem.CreateDirectory(dirname, perm_bits=0o700) 222 223 self._mktemp_retvals.append(dirname) 224 return dirname 225 226 def _TempEntryname(self, suffix, prefix, dir): 227 """Helper function for mk[ds]temp. 228 229 Args: 230 suffix: string, filename suffix 231 prefix: string, filename prefix 232 dir: string, directory for temp dir. Must exist before call 233 Returns: 234 string, entry name 235 """ 236 # default dir affected by "global" 237 if dir is None: 238 call_mkdir = True 239 dir = self.gettempdir() 240 else: 241 call_mkdir = False 242 243 entryname = None 244 while not entryname or self._filesystem.Exists(entryname): 245 entryname = self._TempFilename(suffix=suffix, prefix=prefix, dir=dir) 246 if not call_mkdir: 247 # This is simplistic. A bad input of suffix=/f will cause tempfile 248 # to blow up, but this mock won't. But that's already a broken 249 # corner case 250 parent_dir = os.path.dirname(entryname) 251 try: 252 self._filesystem.GetObject(parent_dir) 253 except IOError as err: 254 assert 'No such file or directory' in str(err) 255 # python -c 'import tempfile; tempfile.mkstemp(dir="/no/such/dr")' 256 # OSError: [Errno 2] No such file or directory: '/no/such/dr/tmpFBuqjO' 257 raise OSError( 258 errno.ENOENT, 259 'No such directory in mock filesystem', 260 parent_dir) 261 return entryname 262 263 # pylint: disable-msg=C6409 264 def gettempdir(self): 265 """Get default temp dir. Sets default if unset.""" 266 if self.tempdir: 267 return self.tempdir 268 # pylint: disable-msg=C6002 269 # TODO: environment variables TMPDIR TEMP TMP, or other dirs? 270 self.tempdir = '/tmp' 271 return self.tempdir 272 273 # pylint: disable-msg=C6409 274 def gettempprefix(self): 275 """Get temp filename prefix. 276 277 NOTE: This has no effect on py2.4 278 279 Returns: 280 string, prefix to use in temporary filenames 281 """ 282 return self._temp_prefix 283 284 # pylint: disable-msg=C6409 285 def mktemp(self, suffix=''): 286 """mktemp is deprecated in 2.4.1, and is thus unimplemented.""" 287 raise NotImplementedError 288 289 def _SetTemplate(self, template): 290 """Setter for 'template' property.""" 291 self._temp_prefix = template 292 logging.error('tempfile.template= is a NOP in python2.4') 293 294 def __SetTemplate(self, template): 295 """Indirect setter for 'template' property.""" 296 self._SetTemplate(template) 297 298 def __DeprecatedTemplate(self): 299 """template property implementation.""" 300 raise NotImplementedError 301 302 # reading from template is deprecated, setting is ok. 303 template = property(__DeprecatedTemplate, __SetTemplate, 304 doc="""Set the prefix for temp filenames""") 305 306 def FakeReturnedMktempValues(self): 307 """For validation purposes, mktemp()'s return values are stored.""" 308 return self._mktemp_retvals 309 310 def FakeMktempReset(self): 311 """Clear the stored mktemp() values.""" 312 self._mktemp_retvals = [] 313