PageRenderTime 51ms CodeModel.GetById 10ms RepoModel.GetById 1ms app.codeStats 0ms

/tests/regressiontests/file_storage/tests.py

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