PageRenderTime 156ms CodeModel.GetById 60ms app.highlight 59ms RepoModel.GetById 31ms app.codeStats 1ms

/tests/regressiontests/file_storage/tests.py

https://code.google.com/p/mango-py/
Python | 452 lines | 426 code | 7 blank | 19 comment | 4 complexity | 6994f1b2968c18a7f3ca0f741ee5aac6 MD5 | raw file
  1# -*- coding: utf-8 -*-
  2import os
  3import shutil
  4import sys
  5import tempfile
  6import time
  7from datetime import datetime, timedelta
  8try:
  9    from cStringIO import StringIO
 10except ImportError:
 11    from StringIO import StringIO
 12
 13try:
 14    import threading
 15except ImportError:
 16    import dummy_threading as threading
 17
 18from django.conf import settings
 19from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured
 20from django.core.files.base import ContentFile, File
 21from django.core.files.images import get_image_dimensions
 22from django.core.files.storage import FileSystemStorage, get_storage_class
 23from django.core.files.uploadedfile import UploadedFile
 24from django.utils import unittest
 25
 26# Try to import PIL in either of the two ways it can end up installed.
 27# Checking for the existence of Image is enough for CPython, but
 28# for PyPy, you need to check for the underlying modules
 29try:
 30    from PIL import Image, _imaging
 31except ImportError:
 32    try:
 33        import Image, _imaging
 34    except ImportError:
 35        Image = None
 36
 37
 38class GetStorageClassTests(unittest.TestCase):
 39    def assertRaisesErrorWithMessage(self, error, message, callable,
 40        *args, **kwargs):
 41        self.assertRaises(error, callable, *args, **kwargs)
 42        try:
 43            callable(*args, **kwargs)
 44        except error, e:
 45            self.assertEqual(message, str(e))
 46
 47    def test_get_filesystem_storage(self):
 48        """
 49        get_storage_class returns the class for a storage backend name/path.
 50        """
 51        self.assertEqual(
 52            get_storage_class('django.core.files.storage.FileSystemStorage'),
 53            FileSystemStorage)
 54
 55    def test_get_invalid_storage_module(self):
 56        """
 57        get_storage_class raises an error if the requested import don't exist.
 58        """
 59        self.assertRaisesErrorWithMessage(
 60            ImproperlyConfigured,
 61            "NonExistingStorage isn't a storage module.",
 62            get_storage_class,
 63            'NonExistingStorage')
 64
 65    def test_get_nonexisting_storage_class(self):
 66        """
 67        get_storage_class raises an error if the requested class don't exist.
 68        """
 69        self.assertRaisesErrorWithMessage(
 70            ImproperlyConfigured,
 71            'Storage module "django.core.files.storage" does not define a '\
 72                '"NonExistingStorage" class.',
 73            get_storage_class,
 74            'django.core.files.storage.NonExistingStorage')
 75
 76    def test_get_nonexisting_storage_module(self):
 77        """
 78        get_storage_class raises an error if the requested module don't exist.
 79        """
 80        # Error message may or may not be the fully qualified path.
 81        self.assertRaisesRegexp(
 82            ImproperlyConfigured,
 83            ('Error importing storage module django.core.files.non_existing_'
 84                'storage: "No module named .*non_existing_storage"'),
 85            get_storage_class,
 86            'django.core.files.non_existing_storage.NonExistingStorage'
 87        )
 88
 89class FileStorageTests(unittest.TestCase):
 90    storage_class = FileSystemStorage
 91
 92    def setUp(self):
 93        self.temp_dir = tempfile.mktemp()
 94        os.makedirs(self.temp_dir)
 95        self.storage = self.storage_class(location=self.temp_dir,
 96            base_url='/test_media_url/')
 97
 98    def tearDown(self):
 99        shutil.rmtree(self.temp_dir)
100
101    def test_file_access_options(self):
102        """
103        Standard file access options are available, and work as expected.
104        """
105        self.assertFalse(self.storage.exists('storage_test'))
106        f = self.storage.open('storage_test', 'w')
107        f.write('storage contents')
108        f.close()
109        self.assertTrue(self.storage.exists('storage_test'))
110
111        f = self.storage.open('storage_test', 'r')
112        self.assertEqual(f.read(), 'storage contents')
113        f.close()
114
115        self.storage.delete('storage_test')
116        self.assertFalse(self.storage.exists('storage_test'))
117
118    def test_file_accessed_time(self):
119        """
120        File storage returns a Datetime object for the last accessed time of
121        a file.
122        """
123        self.assertFalse(self.storage.exists('test.file'))
124
125        f = ContentFile('custom contents')
126        f_name = self.storage.save('test.file', f)
127        atime = self.storage.accessed_time(f_name)
128
129        self.assertEqual(atime, datetime.fromtimestamp(
130            os.path.getatime(self.storage.path(f_name))))
131        self.assertTrue(datetime.now() - self.storage.accessed_time(f_name) < timedelta(seconds=2))
132        self.storage.delete(f_name)
133
134    def test_file_created_time(self):
135        """
136        File storage returns a Datetime object for the creation time of
137        a file.
138        """
139        self.assertFalse(self.storage.exists('test.file'))
140
141        f = ContentFile('custom contents')
142        f_name = self.storage.save('test.file', f)
143        ctime = self.storage.created_time(f_name)
144
145        self.assertEqual(ctime, datetime.fromtimestamp(
146            os.path.getctime(self.storage.path(f_name))))
147        self.assertTrue(datetime.now() - self.storage.created_time(f_name) < timedelta(seconds=2))
148
149        self.storage.delete(f_name)
150
151    def test_file_modified_time(self):
152        """
153        File storage returns a Datetime object for the last modified time of
154        a file.
155        """
156        self.assertFalse(self.storage.exists('test.file'))
157
158        f = ContentFile('custom contents')
159        f_name = self.storage.save('test.file', f)
160        mtime = self.storage.modified_time(f_name)
161
162        self.assertEqual(mtime, datetime.fromtimestamp(
163            os.path.getmtime(self.storage.path(f_name))))
164        self.assertTrue(datetime.now() - self.storage.modified_time(f_name) < timedelta(seconds=2))
165
166        self.storage.delete(f_name)
167
168    def test_file_save_without_name(self):
169        """
170        File storage extracts the filename from the content object if no
171        name is given explicitly.
172        """
173        self.assertFalse(self.storage.exists('test.file'))
174
175        f = ContentFile('custom contents')
176        f.name = 'test.file'
177
178        storage_f_name = self.storage.save(None, f)
179
180        self.assertEqual(storage_f_name, f.name)
181
182        self.assertTrue(os.path.exists(os.path.join(self.temp_dir, f.name)))
183
184        self.storage.delete(storage_f_name)
185
186    def test_file_path(self):
187        """
188        File storage returns the full path of a file
189        """
190        self.assertFalse(self.storage.exists('test.file'))
191
192        f = ContentFile('custom contents')
193        f_name = self.storage.save('test.file', f)
194
195        self.assertEqual(self.storage.path(f_name),
196            os.path.join(self.temp_dir, f_name))
197
198        self.storage.delete(f_name)
199
200    def test_file_url(self):
201        """
202        File storage returns a url to access a given file from the Web.
203        """
204        self.assertEqual(self.storage.url('test.file'),
205            '%s%s' % (self.storage.base_url, 'test.file'))
206
207        # should encode special chars except ~!*()'
208        # like encodeURIComponent() JavaScript function do
209        self.assertEqual(self.storage.url(r"""~!*()'@#$%^&*abc`+=.file"""),
210            """/test_media_url/~!*()'%40%23%24%25%5E%26*abc%60%2B%3D.file""")
211
212        # should stanslate os path separator(s) to the url path separator
213        self.assertEqual(self.storage.url("""a/b\\c.file"""),
214            """/test_media_url/a/b/c.file""")
215
216        self.storage.base_url = None
217        self.assertRaises(ValueError, self.storage.url, 'test.file')
218
219    def test_file_with_mixin(self):
220        """
221        File storage can get a mixin to extend the functionality of the
222        returned file.
223        """
224        self.assertFalse(self.storage.exists('test.file'))
225
226        class TestFileMixin(object):
227            mixed_in = True
228
229        f = ContentFile('custom contents')
230        f_name = self.storage.save('test.file', f)
231
232        self.assertTrue(isinstance(
233            self.storage.open('test.file', mixin=TestFileMixin),
234            TestFileMixin
235        ))
236
237        self.storage.delete('test.file')
238
239    def test_listdir(self):
240        """
241        File storage returns a tuple containing directories and files.
242        """
243        self.assertFalse(self.storage.exists('storage_test_1'))
244        self.assertFalse(self.storage.exists('storage_test_2'))
245        self.assertFalse(self.storage.exists('storage_dir_1'))
246
247        f = self.storage.save('storage_test_1', ContentFile('custom content'))
248        f = self.storage.save('storage_test_2', ContentFile('custom content'))
249        os.mkdir(os.path.join(self.temp_dir, 'storage_dir_1'))
250
251        dirs, files = self.storage.listdir('')
252        self.assertEqual(set(dirs), set([u'storage_dir_1']))
253        self.assertEqual(set(files),
254                         set([u'storage_test_1', u'storage_test_2']))
255
256        self.storage.delete('storage_test_1')
257        self.storage.delete('storage_test_2')
258        os.rmdir(os.path.join(self.temp_dir, 'storage_dir_1'))
259
260    def test_file_storage_prevents_directory_traversal(self):
261        """
262        File storage prevents directory traversal (files can only be accessed if
263        they're below the storage location).
264        """
265        self.assertRaises(SuspiciousOperation, self.storage.exists, '..')
266        self.assertRaises(SuspiciousOperation, self.storage.exists, '/etc/passwd')
267
268class CustomStorage(FileSystemStorage):
269    def get_available_name(self, name):
270        """
271        Append numbers to duplicate files rather than underscores, like Trac.
272        """
273        parts = name.split('.')
274        basename, ext = parts[0], parts[1:]
275        number = 2
276        while self.exists(name):
277            name = '.'.join([basename, str(number)] + ext)
278            number += 1
279
280        return name
281
282class CustomStorageTests(FileStorageTests):
283    storage_class = CustomStorage
284
285    def test_custom_get_available_name(self):
286        first = self.storage.save('custom_storage', ContentFile('custom contents'))
287        self.assertEqual(first, 'custom_storage')
288        second = self.storage.save('custom_storage', ContentFile('more contents'))
289        self.assertEqual(second, 'custom_storage.2')
290        self.storage.delete(first)
291        self.storage.delete(second)
292
293class UnicodeFileNameTests(unittest.TestCase):
294    def test_unicode_file_names(self):
295        """
296        Regression test for #8156: files with unicode names I can't quite figure
297        out the encoding situation between doctest and this file, but the actual
298        repr doesn't matter; it just shouldn't return a unicode object.
299        """
300        uf = UploadedFile(name=u'?C?mo?',content_type='text')
301        self.assertEqual(type(uf.__repr__()), str)
302
303# Tests for a race condition on file saving (#4948).
304# This is written in such a way that it'll always pass on platforms
305# without threading.
306
307class SlowFile(ContentFile):
308    def chunks(self):
309        time.sleep(1)
310        return super(ContentFile, self).chunks()
311
312class FileSaveRaceConditionTest(unittest.TestCase):
313    def setUp(self):
314        self.storage_dir = tempfile.mkdtemp()
315        self.storage = FileSystemStorage(self.storage_dir)
316        self.thread = threading.Thread(target=self.save_file, args=['conflict'])
317
318    def tearDown(self):
319        shutil.rmtree(self.storage_dir)
320
321    def save_file(self, name):
322        name = self.storage.save(name, SlowFile("Data"))
323
324    def test_race_condition(self):
325        self.thread.start()
326        name = self.save_file('conflict')
327        self.thread.join()
328        self.assertTrue(self.storage.exists('conflict'))
329        self.assertTrue(self.storage.exists('conflict_1'))
330        self.storage.delete('conflict')
331        self.storage.delete('conflict_1')
332
333class FileStoragePermissions(unittest.TestCase):
334    def setUp(self):
335        self.old_perms = settings.FILE_UPLOAD_PERMISSIONS
336        settings.FILE_UPLOAD_PERMISSIONS = 0666
337        self.storage_dir = tempfile.mkdtemp()
338        self.storage = FileSystemStorage(self.storage_dir)
339
340    def tearDown(self):
341        settings.FILE_UPLOAD_PERMISSIONS = self.old_perms
342        shutil.rmtree(self.storage_dir)
343
344    def test_file_upload_permissions(self):
345        name = self.storage.save("the_file", ContentFile("data"))
346        actual_mode = os.stat(self.storage.path(name))[0] & 0777
347        self.assertEqual(actual_mode, 0666)
348
349
350class FileStoragePathParsing(unittest.TestCase):
351    def setUp(self):
352        self.storage_dir = tempfile.mkdtemp()
353        self.storage = FileSystemStorage(self.storage_dir)
354
355    def tearDown(self):
356        shutil.rmtree(self.storage_dir)
357
358    def test_directory_with_dot(self):
359        """Regression test for #9610.
360
361        If the directory name contains a dot and the file name doesn't, make
362        sure we still mangle the file name instead of the directory name.
363        """
364
365        self.storage.save('dotted.path/test', ContentFile("1"))
366        self.storage.save('dotted.path/test', ContentFile("2"))
367
368        self.assertFalse(os.path.exists(os.path.join(self.storage_dir, 'dotted_.path')))
369        self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test')))
370        self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test_1')))
371
372    def test_first_character_dot(self):
373        """
374        File names with a dot as their first character don't have an extension,
375        and the underscore should get added to the end.
376        """
377        self.storage.save('dotted.path/.test', ContentFile("1"))
378        self.storage.save('dotted.path/.test', ContentFile("2"))
379
380        self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test')))
381        # Before 2.6, a leading dot was treated as an extension, and so
382        # underscore gets added to beginning instead of end.
383        if sys.version_info < (2, 6):
384            self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/_1.test')))
385        else:
386            self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test_1')))
387
388class DimensionClosingBug(unittest.TestCase):
389    """
390    Test that get_image_dimensions() properly closes files (#8817)
391    """
392    @unittest.skipUnless(Image, "PIL not installed")
393    def test_not_closing_of_files(self):
394        """
395        Open files passed into get_image_dimensions() should stay opened.
396        """
397        empty_io = StringIO()
398        try:
399            get_image_dimensions(empty_io)
400        finally:
401            self.assertTrue(not empty_io.closed)
402
403    @unittest.skipUnless(Image, "PIL not installed")
404    def test_closing_of_filenames(self):
405        """
406        get_image_dimensions() called with a filename should closed the file.
407        """
408        # We need to inject a modified open() builtin into the images module
409        # that checks if the file was closed properly if the function is
410        # called with a filename instead of an file object.
411        # get_image_dimensions will call our catching_open instead of the
412        # regular builtin one.
413
414        class FileWrapper(object):
415            _closed = []
416            def __init__(self, f):
417                self.f = f
418            def __getattr__(self, name):
419                return getattr(self.f, name)
420            def close(self):
421                self._closed.append(True)
422                self.f.close()
423
424        def catching_open(*args):
425            return FileWrapper(open(*args))
426
427        from django.core.files import images
428        images.open = catching_open
429        try:
430            get_image_dimensions(os.path.join(os.path.dirname(__file__), "test1.png"))
431        finally:
432            del images.open
433        self.assertTrue(FileWrapper._closed)
434
435class InconsistentGetImageDimensionsBug(unittest.TestCase):
436    """
437    Test that get_image_dimensions() works properly after various calls
438    using a file handler (#11158)
439    """
440    @unittest.skipUnless(Image, "PIL not installed")
441    def test_multiple_calls(self):
442        """
443        Multiple calls of get_image_dimensions() should return the same size.
444        """
445        from django.core.files.images import ImageFile
446
447        img_path = os.path.join(os.path.dirname(__file__), "test.png")
448        image = ImageFile(open(img_path, 'rb'))
449        image_pil = Image.open(img_path)
450        size_1, size_2 = get_image_dimensions(image), get_image_dimensions(image)
451        self.assertEqual(image_pil.size, size_1)
452        self.assertEqual(size_1, size_2)