diff options
| author | Florian Apolloner <florian@apolloner.eu> | 2013-02-26 09:53:47 +0100 |
|---|---|---|
| committer | Florian Apolloner <florian@apolloner.eu> | 2013-02-26 14:36:57 +0100 |
| commit | 89f40e36246100df6a11316c31a76712ebc6c501 (patch) | |
| tree | 6e65639683ddaf2027908d1ecb1739e0e2ff853b /tests/utils | |
| parent | b3d2ccb5bfbaf6e7fe1f98843baaa48c35a70950 (diff) | |
Merged regressiontests and modeltests into the test root.
Diffstat (limited to 'tests/utils')
38 files changed, 2724 insertions, 0 deletions
diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/utils/__init__.py diff --git a/tests/utils/archive.py b/tests/utils/archive.py new file mode 100644 index 0000000000..8861b4a577 --- /dev/null +++ b/tests/utils/archive.py @@ -0,0 +1,72 @@ +import os +import shutil +import tempfile +from django.utils import unittest + +from django.utils.archive import Archive, extract +from django.utils._os import upath + + +TEST_DIR = os.path.join(os.path.dirname(upath(__file__)), 'archives') + + +class ArchiveTester(object): + archive = None + + def setUp(self): + """ + Create temporary directory for testing extraction. + """ + self.old_cwd = os.getcwd() + self.tmpdir = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, self.tmpdir) + self.archive_path = os.path.join(TEST_DIR, self.archive) + # Always start off in TEST_DIR. + os.chdir(TEST_DIR) + + def tearDown(self): + os.chdir(self.old_cwd) + + def test_extract_method(self): + with Archive(self.archive) as archive: + archive.extract(self.tmpdir) + self.check_files(self.tmpdir) + + def test_extract_method_no_to_path(self): + os.chdir(self.tmpdir) + with Archive(self.archive_path) as archive: + archive.extract() + self.check_files(self.tmpdir) + + def test_extract_function(self): + extract(self.archive_path, self.tmpdir) + self.check_files(self.tmpdir) + + def test_extract_function_no_to_path(self): + os.chdir(self.tmpdir) + extract(self.archive_path) + self.check_files(self.tmpdir) + + def check_files(self, tmpdir): + self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, '1'))) + self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, '2'))) + self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'foo', '1'))) + self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'foo', '2'))) + self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'foo', 'bar', '1'))) + self.assertTrue(os.path.isfile(os.path.join(self.tmpdir, 'foo', 'bar', '2'))) + + +class TestZip(ArchiveTester, unittest.TestCase): + archive = 'foobar.zip' + + +class TestTar(ArchiveTester, unittest.TestCase): + archive = 'foobar.tar' + + +class TestGzipTar(ArchiveTester, unittest.TestCase): + archive = 'foobar.tar.gz' + + +class TestBzip2Tar(ArchiveTester, unittest.TestCase): + archive = 'foobar.tar.bz2' diff --git a/tests/utils/archives/foobar.tar b/tests/utils/archives/foobar.tar Binary files differnew file mode 100644 index 0000000000..5418405070 --- /dev/null +++ b/tests/utils/archives/foobar.tar diff --git a/tests/utils/archives/foobar.tar.bz2 b/tests/utils/archives/foobar.tar.bz2 Binary files differnew file mode 100644 index 0000000000..693529dcef --- /dev/null +++ b/tests/utils/archives/foobar.tar.bz2 diff --git a/tests/utils/archives/foobar.tar.gz b/tests/utils/archives/foobar.tar.gz Binary files differnew file mode 100644 index 0000000000..5b596bd58e --- /dev/null +++ b/tests/utils/archives/foobar.tar.gz diff --git a/tests/utils/archives/foobar.zip b/tests/utils/archives/foobar.zip Binary files differnew file mode 100644 index 0000000000..dc3e2fc067 --- /dev/null +++ b/tests/utils/archives/foobar.zip diff --git a/tests/utils/baseconv.py b/tests/utils/baseconv.py new file mode 100644 index 0000000000..cc413b4e8e --- /dev/null +++ b/tests/utils/baseconv.py @@ -0,0 +1,42 @@ +from unittest import TestCase +from django.utils.baseconv import base2, base16, base36, base56, base62, base64, BaseConverter +from django.utils.six.moves import xrange + +class TestBaseConv(TestCase): + + def test_baseconv(self): + nums = [-10 ** 10, 10 ** 10] + list(xrange(-100, 100)) + for converter in [base2, base16, base36, base56, base62, base64]: + for i in nums: + self.assertEqual(i, converter.decode(converter.encode(i))) + + def test_base11(self): + base11 = BaseConverter('0123456789-', sign='$') + self.assertEqual(base11.encode(1234), '-22') + self.assertEqual(base11.decode('-22'), 1234) + self.assertEqual(base11.encode(-1234), '$-22') + self.assertEqual(base11.decode('$-22'), -1234) + + def test_base20(self): + base20 = BaseConverter('0123456789abcdefghij') + self.assertEqual(base20.encode(1234), '31e') + self.assertEqual(base20.decode('31e'), 1234) + self.assertEqual(base20.encode(-1234), '-31e') + self.assertEqual(base20.decode('-31e'), -1234) + + def test_base64(self): + self.assertEqual(base64.encode(1234), 'JI') + self.assertEqual(base64.decode('JI'), 1234) + self.assertEqual(base64.encode(-1234), '$JI') + self.assertEqual(base64.decode('$JI'), -1234) + + def test_base7(self): + base7 = BaseConverter('cjdhel3', sign='g') + self.assertEqual(base7.encode(1234), 'hejd') + self.assertEqual(base7.decode('hejd'), 1234) + self.assertEqual(base7.encode(-1234), 'ghejd') + self.assertEqual(base7.decode('ghejd'), -1234) + + def test_exception(self): + self.assertRaises(ValueError, BaseConverter, 'abc', sign='a') + self.assertTrue(isinstance(BaseConverter('abc', sign='d'), BaseConverter)) diff --git a/tests/utils/checksums.py b/tests/utils/checksums.py new file mode 100644 index 0000000000..cee6dca2a8 --- /dev/null +++ b/tests/utils/checksums.py @@ -0,0 +1,29 @@ +import unittest + +from django.utils import checksums + +class TestUtilsChecksums(unittest.TestCase): + + def check_output(self, function, value, output=None): + """ + Check that function(value) equals output. If output is None, + check that function(value) equals value. + """ + if output is None: + output = value + self.assertEqual(function(value), output) + + def test_luhn(self): + f = checksums.luhn + items = ( + (4111111111111111, True), ('4111111111111111', True), + (4222222222222, True), (378734493671000, True), + (5424000000000015, True), (5555555555554444, True), + (1008, True), ('0000001008', True), ('000000001008', True), + (4012888888881881, True), (1234567890123456789012345678909, True), + (4111111111211111, False), (42222222222224, False), + (100, False), ('100', False), ('0000100', False), + ('abc', False), (None, False), (object(), False), + ) + for value, output in items: + self.check_output(f, value, output) diff --git a/tests/utils/crypto.py b/tests/utils/crypto.py new file mode 100644 index 0000000000..5cf2934ab0 --- /dev/null +++ b/tests/utils/crypto.py @@ -0,0 +1,146 @@ +from __future__ import unicode_literals + +import binascii +import math +import timeit +import hashlib + +from django.utils import unittest +from django.utils.crypto import constant_time_compare, pbkdf2 + + +class TestUtilsCryptoMisc(unittest.TestCase): + + def test_constant_time_compare(self): + # It's hard to test for constant time, just test the result. + self.assertTrue(constant_time_compare(b'spam', b'spam')) + self.assertFalse(constant_time_compare(b'spam', b'eggs')) + self.assertTrue(constant_time_compare('spam', 'spam')) + self.assertFalse(constant_time_compare('spam', 'eggs')) + + +class TestUtilsCryptoPBKDF2(unittest.TestCase): + + # http://tools.ietf.org/html/draft-josefsson-pbkdf2-test-vectors-06 + rfc_vectors = [ + { + "args": { + "password": "password", + "salt": "salt", + "iterations": 1, + "dklen": 20, + "digest": hashlib.sha1, + }, + "result": "0c60c80f961f0e71f3a9b524af6012062fe037a6", + }, + { + "args": { + "password": "password", + "salt": "salt", + "iterations": 2, + "dklen": 20, + "digest": hashlib.sha1, + }, + "result": "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957", + }, + { + "args": { + "password": "password", + "salt": "salt", + "iterations": 4096, + "dklen": 20, + "digest": hashlib.sha1, + }, + "result": "4b007901b765489abead49d926f721d065a429c1", + }, + # # this takes way too long :( + # { + # "args": { + # "password": "password", + # "salt": "salt", + # "iterations": 16777216, + # "dklen": 20, + # "digest": hashlib.sha1, + # }, + # "result": "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984", + # }, + { + "args": { + "password": "passwordPASSWORDpassword", + "salt": "saltSALTsaltSALTsaltSALTsaltSALTsalt", + "iterations": 4096, + "dklen": 25, + "digest": hashlib.sha1, + }, + "result": "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038", + }, + { + "args": { + "password": "pass\0word", + "salt": "sa\0lt", + "iterations": 4096, + "dklen": 16, + "digest": hashlib.sha1, + }, + "result": "56fa6aa75548099dcc37d7f03425e0c3", + }, + ] + + regression_vectors = [ + { + "args": { + "password": "password", + "salt": "salt", + "iterations": 1, + "dklen": 20, + "digest": hashlib.sha256, + }, + "result": "120fb6cffcf8b32c43e7225256c4f837a86548c9", + }, + { + "args": { + "password": "password", + "salt": "salt", + "iterations": 1, + "dklen": 20, + "digest": hashlib.sha512, + }, + "result": "867f70cf1ade02cff3752599a3a53dc4af34c7a6", + }, + { + "args": { + "password": "password", + "salt": "salt", + "iterations": 1000, + "dklen": 0, + "digest": hashlib.sha512, + }, + "result": ("afe6c5530785b6cc6b1c6453384731bd5ee432ee" + "549fd42fb6695779ad8a1c5bf59de69c48f774ef" + "c4007d5298f9033c0241d5ab69305e7b64eceeb8d" + "834cfec"), + }, + # Check leading zeros are not stripped (#17481) + { + "args": { + "password": b'\xba', + "salt": "salt", + "iterations": 1, + "dklen": 20, + "digest": hashlib.sha1, + }, + "result": '0053d3b91a7f1e54effebd6d68771e8a6e0b2c5b', + }, + ] + + def test_public_vectors(self): + for vector in self.rfc_vectors: + result = pbkdf2(**vector['args']) + self.assertEqual(binascii.hexlify(result).decode('ascii'), + vector['result']) + + def test_regression_vectors(self): + for vector in self.regression_vectors: + result = pbkdf2(**vector['args']) + self.assertEqual(binascii.hexlify(result).decode('ascii'), + vector['result']) diff --git a/tests/utils/datastructures.py b/tests/utils/datastructures.py new file mode 100644 index 0000000000..3161c04f97 --- /dev/null +++ b/tests/utils/datastructures.py @@ -0,0 +1,306 @@ +""" +Tests for stuff in django.utils.datastructures. +""" + +import copy +import pickle +import warnings + +from django.test import SimpleTestCase +from django.utils.datastructures import (DictWrapper, ImmutableList, + MultiValueDict, MultiValueDictKeyError, MergeDict, SortedDict) +from django.utils import six + + +class SortedDictTests(SimpleTestCase): + def setUp(self): + self.d1 = SortedDict() + self.d1[7] = 'seven' + self.d1[1] = 'one' + self.d1[9] = 'nine' + + self.d2 = SortedDict() + self.d2[1] = 'one' + self.d2[9] = 'nine' + self.d2[0] = 'nil' + self.d2[7] = 'seven' + + def test_basic_methods(self): + self.assertEqual(list(six.iterkeys(self.d1)), [7, 1, 9]) + self.assertEqual(list(six.itervalues(self.d1)), ['seven', 'one', 'nine']) + self.assertEqual(list(six.iteritems(self.d1)), [(7, 'seven'), (1, 'one'), (9, 'nine')]) + + def test_overwrite_ordering(self): + """ Overwriting an item keeps its place. """ + self.d1[1] = 'ONE' + self.assertEqual(list(six.itervalues(self.d1)), ['seven', 'ONE', 'nine']) + + def test_append_items(self): + """ New items go to the end. """ + self.d1[0] = 'nil' + self.assertEqual(list(six.iterkeys(self.d1)), [7, 1, 9, 0]) + + def test_delete_and_insert(self): + """ + Deleting an item, then inserting the same key again will place it + at the end. + """ + del self.d2[7] + self.assertEqual(list(six.iterkeys(self.d2)), [1, 9, 0]) + self.d2[7] = 'lucky number 7' + self.assertEqual(list(six.iterkeys(self.d2)), [1, 9, 0, 7]) + + if not six.PY3: + def test_change_keys(self): + """ + Changing the keys won't do anything, it's only a copy of the + keys dict. + + This test doesn't make sense under Python 3 because keys is + an iterator. + """ + k = self.d2.keys() + k.remove(9) + self.assertEqual(self.d2.keys(), [1, 9, 0, 7]) + + def test_init_keys(self): + """ + Initialising a SortedDict with two keys will just take the first one. + + A real dict will actually take the second value so we will too, but + we'll keep the ordering from the first key found. + """ + tuples = ((2, 'two'), (1, 'one'), (2, 'second-two')) + d = SortedDict(tuples) + + self.assertEqual(list(six.iterkeys(d)), [2, 1]) + + real_dict = dict(tuples) + self.assertEqual(sorted(six.itervalues(real_dict)), ['one', 'second-two']) + + # Here the order of SortedDict values *is* what we are testing + self.assertEqual(list(six.itervalues(d)), ['second-two', 'one']) + + def test_overwrite(self): + self.d1[1] = 'not one' + self.assertEqual(self.d1[1], 'not one') + self.assertEqual(list(six.iterkeys(self.d1)), list(six.iterkeys(self.d1.copy()))) + + def test_append(self): + self.d1[13] = 'thirteen' + self.assertEqual( + repr(self.d1), + "{7: 'seven', 1: 'one', 9: 'nine', 13: 'thirteen'}" + ) + + def test_pop(self): + self.assertEqual(self.d1.pop(1, 'missing'), 'one') + self.assertEqual(self.d1.pop(1, 'missing'), 'missing') + + # We don't know which item will be popped in popitem(), so we'll + # just check that the number of keys has decreased. + l = len(self.d1) + self.d1.popitem() + self.assertEqual(l - len(self.d1), 1) + + def test_dict_equality(self): + d = SortedDict((i, i) for i in range(3)) + self.assertEqual(d, {0: 0, 1: 1, 2: 2}) + + def test_tuple_init(self): + d = SortedDict(((1, "one"), (0, "zero"), (2, "two"))) + self.assertEqual(repr(d), "{1: 'one', 0: 'zero', 2: 'two'}") + + def test_pickle(self): + self.assertEqual( + pickle.loads(pickle.dumps(self.d1, 2)), + {7: 'seven', 1: 'one', 9: 'nine'} + ) + + def test_copy(self): + orig = SortedDict(((1, "one"), (0, "zero"), (2, "two"))) + copied = copy.copy(orig) + self.assertEqual(list(six.iterkeys(orig)), [1, 0, 2]) + self.assertEqual(list(six.iterkeys(copied)), [1, 0, 2]) + + def test_clear(self): + self.d1.clear() + self.assertEqual(self.d1, {}) + self.assertEqual(self.d1.keyOrder, []) + + def test_reversed(self): + self.assertEqual(list(self.d1), [7, 1, 9]) + self.assertEqual(list(self.d2), [1, 9, 0, 7]) + self.assertEqual(list(reversed(self.d1)), [9, 1, 7]) + self.assertEqual(list(reversed(self.d2)), [7, 0, 9, 1]) + + def test_insert(self): + d = SortedDict() + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + d.insert(0, "hello", "world") + assert w[0].category is DeprecationWarning + + def test_value_for_index(self): + d = SortedDict({"a": 3}) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + self.assertEqual(d.value_for_index(0), 3) + assert w[0].category is DeprecationWarning + + +class MergeDictTests(SimpleTestCase): + + def test_simple_mergedict(self): + d1 = {'chris':'cool', 'camri':'cute', 'cotton':'adorable', + 'tulip':'snuggable', 'twoofme':'firstone'} + + d2 = {'chris2':'cool2', 'camri2':'cute2', 'cotton2':'adorable2', + 'tulip2':'snuggable2'} + + d3 = {'chris3':'cool3', 'camri3':'cute3', 'cotton3':'adorable3', + 'tulip3':'snuggable3'} + + d4 = {'twoofme': 'secondone'} + + md = MergeDict(d1, d2, d3) + + self.assertEqual(md['chris'], 'cool') + self.assertEqual(md['camri'], 'cute') + self.assertEqual(md['twoofme'], 'firstone') + + md2 = md.copy() + self.assertEqual(md2['chris'], 'cool') + + def test_mergedict_merges_multivaluedict(self): + """ MergeDict can merge MultiValueDicts """ + + multi1 = MultiValueDict({'key1': ['value1'], + 'key2': ['value2', 'value3']}) + + multi2 = MultiValueDict({'key2': ['value4'], + 'key4': ['value5', 'value6']}) + + mm = MergeDict(multi1, multi2) + + # Although 'key2' appears in both dictionaries, + # only the first value is used. + self.assertEqual(mm.getlist('key2'), ['value2', 'value3']) + self.assertEqual(mm.getlist('key4'), ['value5', 'value6']) + self.assertEqual(mm.getlist('undefined'), []) + + self.assertEqual(sorted(six.iterkeys(mm)), ['key1', 'key2', 'key4']) + self.assertEqual(len(list(six.itervalues(mm))), 3) + + self.assertTrue('value1' in six.itervalues(mm)) + + self.assertEqual(sorted(six.iteritems(mm), key=lambda k: k[0]), + [('key1', 'value1'), ('key2', 'value3'), + ('key4', 'value6')]) + + self.assertEqual([(k,mm.getlist(k)) for k in sorted(mm)], + [('key1', ['value1']), + ('key2', ['value2', 'value3']), + ('key4', ['value5', 'value6'])]) + +class MultiValueDictTests(SimpleTestCase): + + def test_multivaluedict(self): + d = MultiValueDict({'name': ['Adrian', 'Simon'], + 'position': ['Developer']}) + + self.assertEqual(d['name'], 'Simon') + self.assertEqual(d.get('name'), 'Simon') + self.assertEqual(d.getlist('name'), ['Adrian', 'Simon']) + self.assertEqual(sorted(list(six.iteritems(d))), + [('name', 'Simon'), ('position', 'Developer')]) + + self.assertEqual(sorted(list(six.iterlists(d))), + [('name', ['Adrian', 'Simon']), + ('position', ['Developer'])]) + + # MultiValueDictKeyError: "Key 'lastname' not found in + # <MultiValueDict: {'position': ['Developer'], + # 'name': ['Adrian', 'Simon']}>" + six.assertRaisesRegex(self, MultiValueDictKeyError, + r'"Key \'lastname\' not found in <MultiValueDict', + d.__getitem__, 'lastname') + + self.assertEqual(d.get('lastname'), None) + self.assertEqual(d.get('lastname', 'nonexistent'), 'nonexistent') + self.assertEqual(d.getlist('lastname'), []) + self.assertEqual(d.getlist('doesnotexist', ['Adrian', 'Simon']), + ['Adrian', 'Simon']) + + d.setlist('lastname', ['Holovaty', 'Willison']) + self.assertEqual(d.getlist('lastname'), ['Holovaty', 'Willison']) + self.assertEqual(sorted(list(six.itervalues(d))), + ['Developer', 'Simon', 'Willison']) + + def test_appendlist(self): + d = MultiValueDict() + d.appendlist('name', 'Adrian') + d.appendlist('name', 'Simon') + self.assertEqual(d.getlist('name'), ['Adrian', 'Simon']) + + def test_copy(self): + for copy_func in [copy.copy, lambda d: d.copy()]: + d1 = MultiValueDict({ + "developers": ["Carl", "Fred"] + }) + self.assertEqual(d1["developers"], "Fred") + d2 = copy_func(d1) + d2.update({"developers": "Groucho"}) + self.assertEqual(d2["developers"], "Groucho") + self.assertEqual(d1["developers"], "Fred") + + d1 = MultiValueDict({ + "key": [[]] + }) + self.assertEqual(d1["key"], []) + d2 = copy_func(d1) + d2["key"].append("Penguin") + self.assertEqual(d1["key"], ["Penguin"]) + self.assertEqual(d2["key"], ["Penguin"]) + + def test_dict_translation(self): + mvd = MultiValueDict({ + 'devs': ['Bob', 'Joe'], + 'pm': ['Rory'], + }) + d = mvd.dict() + self.assertEqual(list(six.iterkeys(d)), list(six.iterkeys(mvd))) + for key in six.iterkeys(mvd): + self.assertEqual(d[key], mvd[key]) + + self.assertEqual({}, MultiValueDict().dict()) + + +class ImmutableListTests(SimpleTestCase): + + def test_sort(self): + d = ImmutableList(range(10)) + + # AttributeError: ImmutableList object is immutable. + self.assertRaisesMessage(AttributeError, + 'ImmutableList object is immutable.', d.sort) + + self.assertEqual(repr(d), '(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)') + + def test_custom_warning(self): + d = ImmutableList(range(10), warning="Object is immutable!") + + self.assertEqual(d[1], 1) + + # AttributeError: Object is immutable! + self.assertRaisesMessage(AttributeError, + 'Object is immutable!', d.__setitem__, 1, 'test') + + +class DictWrapperTests(SimpleTestCase): + + def test_dictwrapper(self): + f = lambda x: "*%s" % x + d = DictWrapper({'a': 'a'}, f, 'xx_') + self.assertEqual("Normal: %(a)s. Modified: %(xx_a)s" % d, + 'Normal: a. Modified: *a') diff --git a/tests/utils/dateformat.py b/tests/utils/dateformat.py new file mode 100644 index 0000000000..0f4fd67f6f --- /dev/null +++ b/tests/utils/dateformat.py @@ -0,0 +1,142 @@ +from __future__ import unicode_literals + +from datetime import datetime, date +import os +import time + +from django.utils.dateformat import format +from django.utils import dateformat, translation, unittest +from django.utils.timezone import utc +from django.utils.tzinfo import FixedOffset, LocalTimezone + + +class DateFormatTests(unittest.TestCase): + def setUp(self): + self.old_TZ = os.environ.get('TZ') + os.environ['TZ'] = 'Europe/Copenhagen' + translation.activate('en-us') + + try: + # Check if a timezone has been set + time.tzset() + self.tz_tests = True + except AttributeError: + # No timezone available. Don't run the tests that require a TZ + self.tz_tests = False + + def tearDown(self): + if self.old_TZ is None: + del os.environ['TZ'] + else: + os.environ['TZ'] = self.old_TZ + + # Cleanup - force re-evaluation of TZ environment variable. + if self.tz_tests: + time.tzset() + + def test_date(self): + d = date(2009, 5, 16) + self.assertEqual(date.fromtimestamp(int(format(d, 'U'))), d) + + def test_naive_datetime(self): + dt = datetime(2009, 5, 16, 5, 30, 30) + self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U'))), dt) + + def test_datetime_with_local_tzinfo(self): + ltz = LocalTimezone(datetime.now()) + dt = datetime(2009, 5, 16, 5, 30, 30, tzinfo=ltz) + self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U')), ltz), dt) + self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U'))), dt.replace(tzinfo=None)) + + def test_datetime_with_tzinfo(self): + tz = FixedOffset(-510) + ltz = LocalTimezone(datetime.now()) + dt = datetime(2009, 5, 16, 5, 30, 30, tzinfo=tz) + self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U')), tz), dt) + self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U')), ltz), dt) + self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U'))), dt.astimezone(ltz).replace(tzinfo=None)) + self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U')), tz).utctimetuple(), dt.utctimetuple()) + self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U')), ltz).utctimetuple(), dt.utctimetuple()) + + def test_epoch(self): + udt = datetime(1970, 1, 1, tzinfo=utc) + self.assertEqual(format(udt, 'U'), '0') + + def test_empty_format(self): + my_birthday = datetime(1979, 7, 8, 22, 00) + + self.assertEqual(dateformat.format(my_birthday, ''), '') + + def test_am_pm(self): + my_birthday = datetime(1979, 7, 8, 22, 00) + + self.assertEqual(dateformat.format(my_birthday, 'a'), 'p.m.') + + def test_microsecond(self): + # Regression test for #18951 + dt = datetime(2009, 5, 16, microsecond=123) + self.assertEqual(dateformat.format(dt, 'u'), '000123') + + def test_date_formats(self): + my_birthday = datetime(1979, 7, 8, 22, 00) + timestamp = datetime(2008, 5, 19, 11, 45, 23, 123456) + + self.assertEqual(dateformat.format(my_birthday, 'A'), 'PM') + self.assertEqual(dateformat.format(timestamp, 'c'), '2008-05-19T11:45:23.123456') + self.assertEqual(dateformat.format(my_birthday, 'd'), '08') + self.assertEqual(dateformat.format(my_birthday, 'j'), '8') + self.assertEqual(dateformat.format(my_birthday, 'l'), 'Sunday') + self.assertEqual(dateformat.format(my_birthday, 'L'), 'False') + self.assertEqual(dateformat.format(my_birthday, 'm'), '07') + self.assertEqual(dateformat.format(my_birthday, 'M'), 'Jul') + self.assertEqual(dateformat.format(my_birthday, 'b'), 'jul') + self.assertEqual(dateformat.format(my_birthday, 'n'), '7') + self.assertEqual(dateformat.format(my_birthday, 'N'), 'July') + + def test_time_formats(self): + my_birthday = datetime(1979, 7, 8, 22, 00) + + self.assertEqual(dateformat.format(my_birthday, 'P'), '10 p.m.') + self.assertEqual(dateformat.format(my_birthday, 's'), '00') + self.assertEqual(dateformat.format(my_birthday, 'S'), 'th') + self.assertEqual(dateformat.format(my_birthday, 't'), '31') + self.assertEqual(dateformat.format(my_birthday, 'w'), '0') + self.assertEqual(dateformat.format(my_birthday, 'W'), '27') + self.assertEqual(dateformat.format(my_birthday, 'y'), '79') + self.assertEqual(dateformat.format(my_birthday, 'Y'), '1979') + self.assertEqual(dateformat.format(my_birthday, 'z'), '189') + + def test_dateformat(self): + my_birthday = datetime(1979, 7, 8, 22, 00) + + self.assertEqual(dateformat.format(my_birthday, r'Y z \C\E\T'), '1979 189 CET') + + self.assertEqual(dateformat.format(my_birthday, r'jS \o\f F'), '8th of July') + + def test_futuredates(self): + the_future = datetime(2100, 10, 25, 0, 00) + self.assertEqual(dateformat.format(the_future, r'Y'), '2100') + + def test_timezones(self): + my_birthday = datetime(1979, 7, 8, 22, 00) + summertime = datetime(2005, 10, 30, 1, 00) + wintertime = datetime(2005, 10, 30, 4, 00) + timestamp = datetime(2008, 5, 19, 11, 45, 23, 123456) + + if self.tz_tests: + self.assertEqual(dateformat.format(my_birthday, 'O'), '+0100') + self.assertEqual(dateformat.format(my_birthday, 'r'), 'Sun, 8 Jul 1979 22:00:00 +0100') + self.assertEqual(dateformat.format(my_birthday, 'T'), 'CET') + self.assertEqual(dateformat.format(my_birthday, 'U'), '300315600') + self.assertEqual(dateformat.format(timestamp, 'u'), '123456') + self.assertEqual(dateformat.format(my_birthday, 'Z'), '3600') + self.assertEqual(dateformat.format(summertime, 'I'), '1') + self.assertEqual(dateformat.format(summertime, 'O'), '+0200') + self.assertEqual(dateformat.format(wintertime, 'I'), '0') + self.assertEqual(dateformat.format(wintertime, 'O'), '+0100') + + # Ticket #16924 -- We don't need timezone support to test this + # 3h30m to the west of UTC + tz = FixedOffset(-3*60 - 30) + dt = datetime(2009, 5, 16, 5, 30, 30, tzinfo=tz) + self.assertEqual(dateformat.format(dt, 'O'), '-0330') diff --git a/tests/utils/dateparse.py b/tests/utils/dateparse.py new file mode 100644 index 0000000000..1a6ca646b8 --- /dev/null +++ b/tests/utils/dateparse.py @@ -0,0 +1,44 @@ +from __future__ import unicode_literals + +from datetime import date, time, datetime + +from django.utils.dateparse import parse_date, parse_time, parse_datetime +from django.utils import unittest +from django.utils.tzinfo import FixedOffset + + +class DateParseTests(unittest.TestCase): + + def test_parse_date(self): + # Valid inputs + self.assertEqual(parse_date('2012-04-23'), date(2012, 4, 23)) + self.assertEqual(parse_date('2012-4-9'), date(2012, 4, 9)) + # Invalid inputs + self.assertEqual(parse_date('20120423'), None) + self.assertRaises(ValueError, parse_date, '2012-04-56') + + def test_parse_time(self): + # Valid inputs + self.assertEqual(parse_time('09:15:00'), time(9, 15)) + self.assertEqual(parse_time('10:10'), time(10, 10)) + self.assertEqual(parse_time('10:20:30.400'), time(10, 20, 30, 400000)) + self.assertEqual(parse_time('4:8:16'), time(4, 8, 16)) + # Invalid inputs + self.assertEqual(parse_time('091500'), None) + self.assertRaises(ValueError, parse_time, '09:15:90') + + def test_parse_datetime(self): + # Valid inputs + self.assertEqual(parse_datetime('2012-04-23T09:15:00'), + datetime(2012, 4, 23, 9, 15)) + self.assertEqual(parse_datetime('2012-4-9 4:8:16'), + datetime(2012, 4, 9, 4, 8, 16)) + self.assertEqual(parse_datetime('2012-04-23T09:15:00Z'), + datetime(2012, 4, 23, 9, 15, 0, 0, FixedOffset(0))) + self.assertEqual(parse_datetime('2012-4-9 4:8:16-0320'), + datetime(2012, 4, 9, 4, 8, 16, 0, FixedOffset(-200))) + self.assertEqual(parse_datetime('2012-04-23T10:20:30.400+02:30'), + datetime(2012, 4, 23, 10, 20, 30, 400000, FixedOffset(150))) + # Invalid inputs + self.assertEqual(parse_datetime('20120423091500'), None) + self.assertRaises(ValueError, parse_datetime, '2012-04-56T09:15:90') diff --git a/tests/utils/datetime_safe.py b/tests/utils/datetime_safe.py new file mode 100644 index 0000000000..c9c34dbf40 --- /dev/null +++ b/tests/utils/datetime_safe.py @@ -0,0 +1,42 @@ +import unittest + +from datetime import date as original_date, datetime as original_datetime +from django.utils.datetime_safe import date, datetime + +class DatetimeTests(unittest.TestCase): + + def setUp(self): + self.just_safe = (1900, 1, 1) + self.just_unsafe = (1899, 12, 31, 23, 59, 59) + self.really_old = (20, 1, 1) + self.more_recent = (2006, 1, 1) + + def test_compare_datetimes(self): + self.assertEqual(original_datetime(*self.more_recent), datetime(*self.more_recent)) + self.assertEqual(original_datetime(*self.really_old), datetime(*self.really_old)) + self.assertEqual(original_date(*self.more_recent), date(*self.more_recent)) + self.assertEqual(original_date(*self.really_old), date(*self.really_old)) + + self.assertEqual(original_date(*self.just_safe).strftime('%Y-%m-%d'), date(*self.just_safe).strftime('%Y-%m-%d')) + self.assertEqual(original_datetime(*self.just_safe).strftime('%Y-%m-%d'), datetime(*self.just_safe).strftime('%Y-%m-%d')) + + def test_safe_strftime(self): + self.assertEqual(date(*self.just_unsafe[:3]).strftime('%Y-%m-%d (weekday %w)'), '1899-12-31 (weekday 0)') + self.assertEqual(date(*self.just_safe).strftime('%Y-%m-%d (weekday %w)'), '1900-01-01 (weekday 1)') + + self.assertEqual(datetime(*self.just_unsafe).strftime('%Y-%m-%d %H:%M:%S (weekday %w)'), '1899-12-31 23:59:59 (weekday 0)') + self.assertEqual(datetime(*self.just_safe).strftime('%Y-%m-%d %H:%M:%S (weekday %w)'), '1900-01-01 00:00:00 (weekday 1)') + + # %y will error before this date + self.assertEqual(date(*self.just_safe).strftime('%y'), '00') + self.assertEqual(datetime(*self.just_safe).strftime('%y'), '00') + + self.assertEqual(date(1850, 8, 2).strftime("%Y/%m/%d was a %A"), '1850/08/02 was a Friday') + + def test_zero_padding(self): + """ + Regression for #12524 + + Check that pre-1000AD dates are padded with zeros if necessary + """ + self.assertEqual(date(1, 1, 1).strftime("%Y/%m/%d was a %A"), '0001/01/01 was a Monday') diff --git a/tests/utils/decorators.py b/tests/utils/decorators.py new file mode 100644 index 0000000000..4d503df026 --- /dev/null +++ b/tests/utils/decorators.py @@ -0,0 +1,108 @@ +from django.http import HttpResponse +from django.template import Template, Context +from django.template.response import TemplateResponse +from django.test import TestCase, RequestFactory +from django.utils.decorators import decorator_from_middleware + + +class ProcessViewMiddleware(object): + def process_view(self, request, view_func, view_args, view_kwargs): + pass + +process_view_dec = decorator_from_middleware(ProcessViewMiddleware) + +@process_view_dec +def process_view(request): + return HttpResponse() + + +class ClassProcessView(object): + def __call__(self, request): + return HttpResponse() + +class_process_view = process_view_dec(ClassProcessView()) + + +class FullMiddleware(object): + def process_request(self, request): + request.process_request_reached = True + + def process_view(sef, request, view_func, view_args, view_kwargs): + request.process_view_reached = True + + def process_template_response(self, request, response): + request.process_template_response_reached = True + return response + + def process_response(self, request, response): + # This should never receive unrendered content. + request.process_response_content = response.content + request.process_response_reached = True + return response + +full_dec = decorator_from_middleware(FullMiddleware) + + +class DecoratorFromMiddlewareTests(TestCase): + """ + Tests for view decorators created using + ``django.utils.decorators.decorator_from_middleware``. + """ + rf = RequestFactory() + + def test_process_view_middleware(self): + """ + Test a middleware that implements process_view. + """ + process_view(self.rf.get('/')) + + def test_callable_process_view_middleware(self): + """ + Test a middleware that implements process_view, operating on a callable class. + """ + class_process_view(self.rf.get('/')) + + def test_full_dec_normal(self): + """ + Test that all methods of middleware are called for normal HttpResponses + """ + + @full_dec + def normal_view(request): + t = Template("Hello world") + return HttpResponse(t.render(Context({}))) + + request = self.rf.get('/') + response = normal_view(request) + self.assertTrue(getattr(request, 'process_request_reached', False)) + self.assertTrue(getattr(request, 'process_view_reached', False)) + # process_template_response must not be called for HttpResponse + self.assertFalse(getattr(request, 'process_template_response_reached', False)) + self.assertTrue(getattr(request, 'process_response_reached', False)) + + def test_full_dec_templateresponse(self): + """ + Test that all methods of middleware are called for TemplateResponses in + the right sequence. + """ + + @full_dec + def template_response_view(request): + t = Template("Hello world") + return TemplateResponse(request, t, {}) + + request = self.rf.get('/') + response = template_response_view(request) + self.assertTrue(getattr(request, 'process_request_reached', False)) + self.assertTrue(getattr(request, 'process_view_reached', False)) + self.assertTrue(getattr(request, 'process_template_response_reached', False)) + # response must not be rendered yet. + self.assertFalse(response._is_rendered) + # process_response must not be called until after response is rendered, + # otherwise some decorators like csrf_protect and gzip_page will not + # work correctly. See #16004 + self.assertFalse(getattr(request, 'process_response_reached', False)) + response.render() + self.assertTrue(getattr(request, 'process_response_reached', False)) + # Check that process_response saw the rendered content + self.assertEqual(request.process_response_content, b"Hello world") diff --git a/tests/utils/eggs/test_egg.egg b/tests/utils/eggs/test_egg.egg Binary files differnew file mode 100644 index 0000000000..9b08cc10ef --- /dev/null +++ b/tests/utils/eggs/test_egg.egg diff --git a/tests/utils/encoding.py b/tests/utils/encoding.py new file mode 100644 index 0000000000..d191845518 --- /dev/null +++ b/tests/utils/encoding.py @@ -0,0 +1,17 @@ +# -*- encoding: utf-8 -*- +from __future__ import unicode_literals + +from django.utils import unittest +from django.utils.encoding import force_bytes + + +class TestEncodingUtils(unittest.TestCase): + def test_force_bytes_exception(self): + """ + Test that force_bytes knows how to convert to bytes an exception + containing non-ASCII characters in its args. + """ + error_msg = "This is an exception, voilà" + exc = ValueError(error_msg) + result = force_bytes(exc) + self.assertEqual(result, error_msg.encode('utf-8')) diff --git a/tests/utils/feedgenerator.py b/tests/utils/feedgenerator.py new file mode 100644 index 0000000000..bcd53bb2a0 --- /dev/null +++ b/tests/utils/feedgenerator.py @@ -0,0 +1,117 @@ +from __future__ import unicode_literals + +import datetime + +from django.utils import feedgenerator, tzinfo, unittest + +class FeedgeneratorTest(unittest.TestCase): + """ + Tests for the low-level syndication feed framework. + """ + + def test_get_tag_uri(self): + """ + Test get_tag_uri() correctly generates TagURIs. + """ + self.assertEqual( + feedgenerator.get_tag_uri('http://example.org/foo/bar#headline', datetime.date(2004, 10, 25)), + 'tag:example.org,2004-10-25:/foo/bar/headline') + + def test_get_tag_uri_with_port(self): + """ + Test that get_tag_uri() correctly generates TagURIs from URLs with port + numbers. + """ + self.assertEqual( + feedgenerator.get_tag_uri('http://www.example.org:8000/2008/11/14/django#headline', datetime.datetime(2008, 11, 14, 13, 37, 0)), + 'tag:www.example.org,2008-11-14:/2008/11/14/django/headline') + + def test_rfc2822_date(self): + """ + Test rfc2822_date() correctly formats datetime objects. + """ + self.assertEqual( + feedgenerator.rfc2822_date(datetime.datetime(2008, 11, 14, 13, 37, 0)), + "Fri, 14 Nov 2008 13:37:00 -0000" + ) + + def test_rfc2822_date_with_timezone(self): + """ + Test rfc2822_date() correctly formats datetime objects with tzinfo. + """ + self.assertEqual( + feedgenerator.rfc2822_date(datetime.datetime(2008, 11, 14, 13, 37, 0, tzinfo=tzinfo.FixedOffset(datetime.timedelta(minutes=60)))), + "Fri, 14 Nov 2008 13:37:00 +0100" + ) + + def test_rfc2822_date_without_time(self): + """ + Test rfc2822_date() correctly formats date objects. + """ + self.assertEqual( + feedgenerator.rfc2822_date(datetime.date(2008, 11, 14)), + "Fri, 14 Nov 2008 00:00:00 -0000" + ) + + def test_rfc3339_date(self): + """ + Test rfc3339_date() correctly formats datetime objects. + """ + self.assertEqual( + feedgenerator.rfc3339_date(datetime.datetime(2008, 11, 14, 13, 37, 0)), + "2008-11-14T13:37:00Z" + ) + + def test_rfc3339_date_with_timezone(self): + """ + Test rfc3339_date() correctly formats datetime objects with tzinfo. + """ + self.assertEqual( + feedgenerator.rfc3339_date(datetime.datetime(2008, 11, 14, 13, 37, 0, tzinfo=tzinfo.FixedOffset(datetime.timedelta(minutes=120)))), + "2008-11-14T13:37:00+02:00" + ) + + def test_rfc3339_date_without_time(self): + """ + Test rfc3339_date() correctly formats date objects. + """ + self.assertEqual( + feedgenerator.rfc3339_date(datetime.date(2008, 11, 14)), + "2008-11-14T00:00:00Z" + ) + + def test_atom1_mime_type(self): + """ + Test to make sure Atom MIME type has UTF8 Charset parameter set + """ + atom_feed = feedgenerator.Atom1Feed("title", "link", "description") + self.assertEqual( + atom_feed.mime_type, "application/atom+xml; charset=utf-8" + ) + + def test_rss_mime_type(self): + """ + Test to make sure RSS MIME type has UTF8 Charset parameter set + """ + rss_feed = feedgenerator.Rss201rev2Feed("title", "link", "description") + self.assertEqual( + rss_feed.mime_type, "application/rss+xml; charset=utf-8" + ) + + # Two regression tests for #14202 + + def test_feed_without_feed_url_gets_rendered_without_atom_link(self): + feed = feedgenerator.Rss201rev2Feed('title', '/link/', 'descr') + self.assertEqual(feed.feed['feed_url'], None) + feed_content = feed.writeString('utf-8') + self.assertNotIn('<atom:link', feed_content) + self.assertNotIn('href="/feed/"', feed_content) + self.assertNotIn('rel="self"', feed_content) + + def test_feed_with_feed_url_gets_rendered_with_atom_link(self): + feed = feedgenerator.Rss201rev2Feed('title', '/link/', 'descr', feed_url='/feed/') + self.assertEqual(feed.feed['feed_url'], '/feed/') + feed_content = feed.writeString('utf-8') + self.assertIn('<atom:link', feed_content) + self.assertIn('href="/feed/"', feed_content) + self.assertIn('rel="self"', feed_content) diff --git a/tests/utils/functional.py b/tests/utils/functional.py new file mode 100644 index 0000000000..3bb50007c6 --- /dev/null +++ b/tests/utils/functional.py @@ -0,0 +1,66 @@ +from django.utils import unittest +from django.utils.functional import lazy, lazy_property, cached_property + + +class FunctionalTestCase(unittest.TestCase): + def test_lazy(self): + t = lazy(lambda: tuple(range(3)), list, tuple) + for a, b in zip(t(), range(3)): + self.assertEqual(a, b) + + def test_lazy_base_class(self): + """Test that lazy also finds base class methods in the proxy object""" + + class Base(object): + def base_method(self): + pass + + class Klazz(Base): + pass + + t = lazy(lambda: Klazz(), Klazz)() + self.assertTrue('base_method' in dir(t)) + + def test_lazy_property(self): + + class A(object): + + def _get_do(self): + raise NotImplementedError + def _set_do(self, value): + raise NotImplementedError + do = lazy_property(_get_do, _set_do) + + class B(A): + def _get_do(self): + return "DO IT" + + self.assertRaises(NotImplementedError, lambda: A().do) + self.assertEqual(B().do, 'DO IT') + + def test_cached_property(self): + """ + Test that cached_property caches its value, + and that it behaves like a property + """ + + class A(object): + + @cached_property + def value(self): + return 1, object() + + a = A() + + # check that it is cached + self.assertEqual(a.value, a.value) + + # check that it returns the right thing + self.assertEqual(a.value[0], 1) + + # check that state isn't shared between instances + a2 = A() + self.assertNotEqual(a.value, a2.value) + + # check that it behaves like a property when there's no instance + self.assertIsInstance(A.value, cached_property) diff --git a/tests/utils/html.py b/tests/utils/html.py new file mode 100644 index 0000000000..62c7dac24a --- /dev/null +++ b/tests/utils/html.py @@ -0,0 +1,161 @@ +from __future__ import unicode_literals + +import unittest + +from django.utils import html + +class TestUtilsHtml(unittest.TestCase): + + def check_output(self, function, value, output=None): + """ + Check that function(value) equals output. If output is None, + check that function(value) equals value. + """ + if output is None: + output = value + self.assertEqual(function(value), output) + + def test_escape(self): + f = html.escape + items = ( + ('&','&'), + ('<', '<'), + ('>', '>'), + ('"', '"'), + ("'", '''), + ) + # Substitution patterns for testing the above items. + patterns = ("%s", "asdf%sfdsa", "%s1", "1%sb") + for value, output in items: + for pattern in patterns: + self.check_output(f, pattern % value, pattern % output) + # Check repeated values. + self.check_output(f, value * 2, output * 2) + # Verify it doesn't double replace &. + self.check_output(f, '<&', '<&') + + def test_format_html(self): + self.assertEqual( + html.format_html("{0} {1} {third} {fourth}", + "< Dangerous >", + html.mark_safe("<b>safe</b>"), + third="< dangerous again", + fourth=html.mark_safe("<i>safe again</i>") + ), + "< Dangerous > <b>safe</b> < dangerous again <i>safe again</i>" + ) + + def test_linebreaks(self): + f = html.linebreaks + items = ( + ("para1\n\npara2\r\rpara3", "<p>para1</p>\n\n<p>para2</p>\n\n<p>para3</p>"), + ("para1\nsub1\rsub2\n\npara2", "<p>para1<br />sub1<br />sub2</p>\n\n<p>para2</p>"), + ("para1\r\n\r\npara2\rsub1\r\rpara4", "<p>para1</p>\n\n<p>para2<br />sub1</p>\n\n<p>para4</p>"), + ("para1\tmore\n\npara2", "<p>para1\tmore</p>\n\n<p>para2</p>"), + ) + for value, output in items: + self.check_output(f, value, output) + + def test_strip_tags(self): + f = html.strip_tags + items = ( + ('<adf>a', 'a'), + ('</adf>a', 'a'), + ('<asdf><asdf>e', 'e'), + ('<f', '<f'), + ('</fe', '</fe'), + ('<x>b<y>', 'b'), + ('a<p onclick="alert(\'<test>\')">b</p>c', 'abc'), + ('a<p a >b</p>c', 'abc'), + ('d<a:b c:d>e</p>f', 'def'), + ('<strong>foo</strong><a href="http://example.com">bar</a>', 'foobar'), + ) + for value, output in items: + self.check_output(f, value, output) + + def test_strip_spaces_between_tags(self): + f = html.strip_spaces_between_tags + # Strings that should come out untouched. + items = (' <adf>', '<adf> ', ' </adf> ', ' <f> x</f>') + for value in items: + self.check_output(f, value) + # Strings that have spaces to strip. + items = ( + ('<d> </d>', '<d></d>'), + ('<p>hello </p>\n<p> world</p>', '<p>hello </p><p> world</p>'), + ('\n<p>\t</p>\n<p> </p>\n', '\n<p></p><p></p>\n'), + ) + for value, output in items: + self.check_output(f, value, output) + + def test_strip_entities(self): + f = html.strip_entities + # Strings that should come out untouched. + values = ("&", "&a", "&a", "a&#a") + for value in values: + self.check_output(f, value) + # Valid entities that should be stripped from the patterns. + entities = ("", "", "&a;", "&fdasdfasdfasdf;") + patterns = ( + ("asdf %(entity)s ", "asdf "), + ("%(entity)s%(entity)s", ""), + ("&%(entity)s%(entity)s", "&"), + ("%(entity)s3", "3"), + ) + for entity in entities: + for in_pattern, output in patterns: + self.check_output(f, in_pattern % {'entity': entity}, output) + + def test_fix_ampersands(self): + f = html.fix_ampersands + # Strings without ampersands or with ampersands already encoded. + values = ("a", "b", "&a;", "& &x; ", "asdf") + patterns = ( + ("%s", "%s"), + ("&%s", "&%s"), + ("&%s&", "&%s&"), + ) + for value in values: + for in_pattern, out_pattern in patterns: + self.check_output(f, in_pattern % value, out_pattern % value) + # Strings with ampersands that need encoding. + items = ( + ("&#;", "&#;"), + ("ͫ ;", "&#875 ;"), + ("abc;", "&#4abc;"), + ) + for value, output in items: + self.check_output(f, value, output) + + def test_escapejs(self): + f = html.escapejs + items = ( + ('"double quotes" and \'single quotes\'', '\\u0022double quotes\\u0022 and \\u0027single quotes\\u0027'), + (r'\ : backslashes, too', '\\u005C : backslashes, too'), + ('and lots of whitespace: \r\n\t\v\f\b', 'and lots of whitespace: \\u000D\\u000A\\u0009\\u000B\\u000C\\u0008'), + (r'<script>and this</script>', '\\u003Cscript\\u003Eand this\\u003C/script\\u003E'), + ('paragraph separator:\u2029and line separator:\u2028', 'paragraph separator:\\u2029and line separator:\\u2028'), + ) + for value, output in items: + self.check_output(f, value, output) + + def test_clean_html(self): + f = html.clean_html + items = ( + ('<p>I <i>believe</i> in <b>semantic markup</b>!</p>', '<p>I <em>believe</em> in <strong>semantic markup</strong>!</p>'), + ('I escape & I don\'t <a href="#" target="_blank">target</a>', 'I escape & I don\'t <a href="#" >target</a>'), + ('<p>I kill whitespace</p><br clear="all"><p> </p>', '<p>I kill whitespace</p>'), + # also a regression test for #7267: this used to raise an UnicodeDecodeError + ('<p>* foo</p><p>* bar</p>', '<ul>\n<li> foo</li><li> bar</li>\n</ul>'), + ) + for value, output in items: + self.check_output(f, value, output) + + def test_remove_tags(self): + f = html.remove_tags + items = ( + ("<b><i>Yes</i></b>", "b i", "Yes"), + ("<a>x</a> <p><b>y</b></p>", "a b", "x <p>y</p>"), + ) + for value, tags, output in items: + self.assertEqual(f(value, tags), output) diff --git a/tests/utils/http.py b/tests/utils/http.py new file mode 100644 index 0000000000..6d3bc025af --- /dev/null +++ b/tests/utils/http.py @@ -0,0 +1,163 @@ +from datetime import datetime +import sys + +from django.http import HttpResponse, utils +from django.test import RequestFactory +from django.utils.datastructures import MultiValueDict +from django.utils import http +from django.utils import six +from django.utils import unittest + + +class TestUtilsHttp(unittest.TestCase): + + def test_same_origin_true(self): + # Identical + self.assertTrue(http.same_origin('http://foo.com/', 'http://foo.com/')) + # One with trailing slash - see #15617 + self.assertTrue(http.same_origin('http://foo.com', 'http://foo.com/')) + self.assertTrue(http.same_origin('http://foo.com/', 'http://foo.com')) + # With port + self.assertTrue(http.same_origin('https://foo.com:8000', 'https://foo.com:8000/')) + + def test_same_origin_false(self): + # Different scheme + self.assertFalse(http.same_origin('http://foo.com', 'https://foo.com')) + # Different host + self.assertFalse(http.same_origin('http://foo.com', 'http://goo.com')) + # Different host again + self.assertFalse(http.same_origin('http://foo.com', 'http://foo.com.evil.com')) + # Different port + self.assertFalse(http.same_origin('http://foo.com:8000', 'http://foo.com:8001')) + + def test_urlencode(self): + # 2-tuples (the norm) + result = http.urlencode((('a', 1), ('b', 2), ('c', 3))) + self.assertEqual(result, 'a=1&b=2&c=3') + + # A dictionary + result = http.urlencode({ 'a': 1, 'b': 2, 'c': 3}) + acceptable_results = [ + # Need to allow all of these as dictionaries have to be treated as + # unordered + 'a=1&b=2&c=3', + 'a=1&c=3&b=2', + 'b=2&a=1&c=3', + 'b=2&c=3&a=1', + 'c=3&a=1&b=2', + 'c=3&b=2&a=1' + ] + self.assertTrue(result in acceptable_results) + result = http.urlencode({'a': [1, 2]}, doseq=False) + self.assertEqual(result, 'a=%5B%271%27%2C+%272%27%5D') + result = http.urlencode({'a': [1, 2]}, doseq=True) + self.assertEqual(result, 'a=1&a=2') + result = http.urlencode({'a': []}, doseq=True) + self.assertEqual(result, '') + + # A MultiValueDict + result = http.urlencode(MultiValueDict({ + 'name': ['Adrian', 'Simon'], + 'position': ['Developer'] + }), doseq=True) + acceptable_results = [ + # MultiValueDicts are similarly unordered + 'name=Adrian&name=Simon&position=Developer', + 'position=Developer&name=Adrian&name=Simon' + ] + self.assertTrue(result in acceptable_results) + + def test_fix_IE_for_vary(self): + """ + Regression for #16632. + + `fix_IE_for_vary` shouldn't crash when there's no Content-Type header. + """ + + # functions to generate responses + def response_with_unsafe_content_type(): + r = HttpResponse(content_type="text/unsafe") + r['Vary'] = 'Cookie' + return r + + def no_content_response_with_unsafe_content_type(): + # 'Content-Type' always defaulted, so delete it + r = response_with_unsafe_content_type() + del r['Content-Type'] + return r + + # request with & without IE user agent + rf = RequestFactory() + request = rf.get('/') + ie_request = rf.get('/', HTTP_USER_AGENT='MSIE') + + # not IE, unsafe_content_type + response = response_with_unsafe_content_type() + utils.fix_IE_for_vary(request, response) + self.assertTrue('Vary' in response) + + # IE, unsafe_content_type + response = response_with_unsafe_content_type() + utils.fix_IE_for_vary(ie_request, response) + self.assertFalse('Vary' in response) + + # not IE, no_content + response = no_content_response_with_unsafe_content_type() + utils.fix_IE_for_vary(request, response) + self.assertTrue('Vary' in response) + + # IE, no_content + response = no_content_response_with_unsafe_content_type() + utils.fix_IE_for_vary(ie_request, response) + self.assertFalse('Vary' in response) + + def test_base36(self): + # reciprocity works + for n in [0, 1, 1000, 1000000]: + self.assertEqual(n, http.base36_to_int(http.int_to_base36(n))) + if not six.PY3: + self.assertEqual(sys.maxint, http.base36_to_int(http.int_to_base36(sys.maxint))) + + # bad input + self.assertRaises(ValueError, http.int_to_base36, -1) + if not six.PY3: + self.assertRaises(ValueError, http.int_to_base36, sys.maxint + 1) + for n in ['1', 'foo', {1: 2}, (1, 2, 3), 3.141]: + self.assertRaises(TypeError, http.int_to_base36, n) + + for n in ['#', ' ']: + self.assertRaises(ValueError, http.base36_to_int, n) + for n in [123, {1: 2}, (1, 2, 3), 3.141]: + self.assertRaises(TypeError, http.base36_to_int, n) + + # more explicit output testing + for n, b36 in [(0, '0'), (1, '1'), (42, '16'), (818469960, 'django')]: + self.assertEqual(http.int_to_base36(n), b36) + self.assertEqual(http.base36_to_int(b36), n) + + +class ETagProcessingTests(unittest.TestCase): + def testParsing(self): + etags = http.parse_etags(r'"", "etag", "e\"t\"ag", "e\\tag", W/"weak"') + self.assertEqual(etags, ['', 'etag', 'e"t"ag', r'e\tag', 'weak']) + + def testQuoting(self): + quoted_etag = http.quote_etag(r'e\t"ag') + self.assertEqual(quoted_etag, r'"e\\t\"ag"') + + +class HttpDateProcessingTests(unittest.TestCase): + def testParsingRfc1123(self): + parsed = http.parse_http_date('Sun, 06 Nov 1994 08:49:37 GMT') + self.assertEqual(datetime.utcfromtimestamp(parsed), + datetime(1994, 11, 6, 8, 49, 37)) + + def testParsingRfc850(self): + parsed = http.parse_http_date('Sunday, 06-Nov-94 08:49:37 GMT') + self.assertEqual(datetime.utcfromtimestamp(parsed), + datetime(1994, 11, 6, 8, 49, 37)) + + def testParsingAsctime(self): + parsed = http.parse_http_date('Sun Nov 6 08:49:37 1994') + self.assertEqual(datetime.utcfromtimestamp(parsed), + datetime(1994, 11, 6, 8, 49, 37)) diff --git a/tests/utils/ipv6.py b/tests/utils/ipv6.py new file mode 100644 index 0000000000..1713de82b8 --- /dev/null +++ b/tests/utils/ipv6.py @@ -0,0 +1,53 @@ +from __future__ import unicode_literals + +from django.utils import unittest +from django.utils.ipv6 import is_valid_ipv6_address, clean_ipv6_address + +class TestUtilsIPv6(unittest.TestCase): + + def test_validates_correct_plain_address(self): + self.assertTrue(is_valid_ipv6_address('fe80::223:6cff:fe8a:2e8a')) + self.assertTrue(is_valid_ipv6_address('2a02::223:6cff:fe8a:2e8a')) + self.assertTrue(is_valid_ipv6_address('1::2:3:4:5:6:7')) + self.assertTrue(is_valid_ipv6_address('::')) + self.assertTrue(is_valid_ipv6_address('::a')) + self.assertTrue(is_valid_ipv6_address('2::')) + + def test_validates_correct_with_v4mapping(self): + self.assertTrue(is_valid_ipv6_address('::ffff:254.42.16.14')) + self.assertTrue(is_valid_ipv6_address('::ffff:0a0a:0a0a')) + + def test_validates_incorrect_plain_address(self): + self.assertFalse(is_valid_ipv6_address('foo')) + self.assertFalse(is_valid_ipv6_address('127.0.0.1')) + self.assertFalse(is_valid_ipv6_address('12345::')) + self.assertFalse(is_valid_ipv6_address('1::2:3::4')) + self.assertFalse(is_valid_ipv6_address('1::zzz')) + self.assertFalse(is_valid_ipv6_address('1::2:3:4:5:6:7:8')) + self.assertFalse(is_valid_ipv6_address('1:2')) + self.assertFalse(is_valid_ipv6_address('1:::2')) + + def test_validates_incorrect_with_v4mapping(self): + self.assertFalse(is_valid_ipv6_address('::ffff:999.42.16.14')) + self.assertFalse(is_valid_ipv6_address('::ffff:zzzz:0a0a')) + # The ::1.2.3.4 format used to be valid but was deprecated + # in rfc4291 section 2.5.5.1 + self.assertTrue(is_valid_ipv6_address('::254.42.16.14')) + self.assertTrue(is_valid_ipv6_address('::0a0a:0a0a')) + self.assertFalse(is_valid_ipv6_address('::999.42.16.14')) + self.assertFalse(is_valid_ipv6_address('::zzzz:0a0a')) + + def test_cleanes_plain_address(self): + self.assertEqual(clean_ipv6_address('DEAD::0:BEEF'), 'dead::beef') + self.assertEqual(clean_ipv6_address('2001:000:a:0000:0:fe:fe:beef'), '2001:0:a::fe:fe:beef') + self.assertEqual(clean_ipv6_address('2001::a:0000:0:fe:fe:beef'), '2001:0:a::fe:fe:beef') + + def test_cleanes_with_v4_mapping(self): + self.assertEqual(clean_ipv6_address('::ffff:0a0a:0a0a'), '::ffff:10.10.10.10') + self.assertEqual(clean_ipv6_address('::ffff:1234:1234'), '::ffff:18.52.18.52') + self.assertEqual(clean_ipv6_address('::ffff:18.52.18.52'), '::ffff:18.52.18.52') + + def test_unpacks_ipv4(self): + self.assertEqual(clean_ipv6_address('::ffff:0a0a:0a0a', unpack_ipv4=True), '10.10.10.10') + self.assertEqual(clean_ipv6_address('::ffff:1234:1234', unpack_ipv4=True), '18.52.18.52') + self.assertEqual(clean_ipv6_address('::ffff:18.52.18.52', unpack_ipv4=True), '18.52.18.52') diff --git a/tests/utils/jslex.py b/tests/utils/jslex.py new file mode 100644 index 0000000000..a2af7b4bcf --- /dev/null +++ b/tests/utils/jslex.py @@ -0,0 +1,216 @@ +"""Tests for jslex.""" +# encoding: utf-8 +# originally from https://bitbucket.org/ned/jslex + +from django.test import TestCase +from django.utils.jslex import JsLexer, prepare_js_for_gettext + +class JsTokensTest(TestCase): + LEX_CASES = [ + # ids + ("a ABC $ _ a123", ["id a", "id ABC", "id $", "id _", "id a123"]), + (r"\u1234 abc\u0020 \u0065_\u0067", [r"id \u1234", r"id abc\u0020", r"id \u0065_\u0067"]), + # numbers + ("123 1.234 0.123e-3 0 1E+40 1e1 .123", ["dnum 123", "dnum 1.234", "dnum 0.123e-3", "dnum 0", "dnum 1E+40", "dnum 1e1", "dnum .123"]), + ("0x1 0xabCD 0XABcd", ["hnum 0x1", "hnum 0xabCD", "hnum 0XABcd"]), + ("010 0377 090", ["onum 010", "onum 0377", "dnum 0", "dnum 90"]), + ("0xa123ghi", ["hnum 0xa123", "id ghi"]), + # keywords + ("function Function FUNCTION", ["keyword function", "id Function", "id FUNCTION"]), + ("const constructor in inherits", ["keyword const", "id constructor", "keyword in", "id inherits"]), + ("true true_enough", ["reserved true", "id true_enough"]), + # strings + (''' 'hello' "hello" ''', ["string 'hello'", 'string "hello"']), + (r""" 'don\'t' "don\"t" '"' "'" '\'' "\"" """, + [r"""string 'don\'t'""", r'''string "don\"t"''', r"""string '"'""", r'''string "'"''', r"""string '\''""", r'''string "\""''']), + (r'"ƃuıxǝ⅂ ʇdıɹɔsɐʌɐſ\""', [r'string "ƃuıxǝ⅂ ʇdıɹɔsɐʌɐſ\""']), + # comments + ("a//b", ["id a", "linecomment //b"]), + ("/****/a/=2//hello", ["comment /****/", "id a", "punct /=", "dnum 2", "linecomment //hello"]), + ("/*\n * Header\n */\na=1;", ["comment /*\n * Header\n */", "id a", "punct =", "dnum 1", "punct ;"]), + # punctuation + ("a+++b", ["id a", "punct ++", "punct +", "id b"]), + # regex + (r"a=/a*/,1", ["id a", "punct =", "regex /a*/", "punct ,", "dnum 1"]), + (r"a=/a*[^/]+/,1", ["id a", "punct =", "regex /a*[^/]+/", "punct ,", "dnum 1"]), + (r"a=/a*\[^/,1", ["id a", "punct =", r"regex /a*\[^/", "punct ,", "dnum 1"]), + (r"a=/\//,1", ["id a", "punct =", r"regex /\//", "punct ,", "dnum 1"]), + + # next two are from http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions + ("""for (var x = a in foo && "</x>" || mot ? z:/x:3;x<5;y</g/i) {xyz(x++);}""", + ["keyword for", "punct (", "keyword var", "id x", "punct =", "id a", "keyword in", + "id foo", "punct &&", 'string "</x>"', "punct ||", "id mot", "punct ?", "id z", + "punct :", "regex /x:3;x<5;y</g", "punct /", "id i", "punct )", "punct {", + "id xyz", "punct (", "id x", "punct ++", "punct )", "punct ;", "punct }"]), + ("""for (var x = a in foo && "</x>" || mot ? z/x:3;x<5;y</g/i) {xyz(x++);}""", + ["keyword for", "punct (", "keyword var", "id x", "punct =", "id a", "keyword in", + "id foo", "punct &&", 'string "</x>"', "punct ||", "id mot", "punct ?", "id z", + "punct /", "id x", "punct :", "dnum 3", "punct ;", "id x", "punct <", "dnum 5", + "punct ;", "id y", "punct <", "regex /g/i", "punct )", "punct {", + "id xyz", "punct (", "id x", "punct ++", "punct )", "punct ;", "punct }"]), + + # Various "illegal" regexes that are valid according to the std. + (r"""/????/, /++++/, /[----]/ """, ["regex /????/", "punct ,", "regex /++++/", "punct ,", "regex /[----]/"]), + + # Stress cases from http://stackoverflow.com/questions/5533925/what-javascript-constructs-does-jslex-incorrectly-lex/5573409#5573409 + (r"""/\[/""", [r"""regex /\[/"""]), + (r"""/[i]/""", [r"""regex /[i]/"""]), + (r"""/[\]]/""", [r"""regex /[\]]/"""]), + (r"""/a[\]]/""", [r"""regex /a[\]]/"""]), + (r"""/a[\]]b/""", [r"""regex /a[\]]b/"""]), + (r"""/[\]/]/gi""", [r"""regex /[\]/]/gi"""]), + (r"""/\[[^\]]+\]/gi""", [r"""regex /\[[^\]]+\]/gi"""]), + (""" + rexl.re = { + NAME: /^(?!\d)(?:\w)+|^"(?:[^"]|"")+"/, + UNQUOTED_LITERAL: /^@(?:(?!\d)(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/, + QUOTED_LITERAL: /^'(?:[^']|'')*'/, + NUMERIC_LITERAL: /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/, + SYMBOL: /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/ + }; + """, + ["id rexl", "punct .", "id re", "punct =", "punct {", + "id NAME", "punct :", r"""regex /^(?!\d)(?:\w)+|^"(?:[^"]|"")+"/""", "punct ,", + "id UNQUOTED_LITERAL", "punct :", r"""regex /^@(?:(?!\d)(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/""", "punct ,", + "id QUOTED_LITERAL", "punct :", r"""regex /^'(?:[^']|'')*'/""", "punct ,", + "id NUMERIC_LITERAL", "punct :", r"""regex /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/""", "punct ,", + "id SYMBOL", "punct :", r"""regex /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/""", + "punct }", "punct ;" + ]), + + (""" + rexl.re = { + NAME: /^(?!\d)(?:\w)+|^"(?:[^"]|"")+"/, + UNQUOTED_LITERAL: /^@(?:(?!\d)(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/, + QUOTED_LITERAL: /^'(?:[^']|'')*'/, + NUMERIC_LITERAL: /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/, + SYMBOL: /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/ + }; + str = '"'; + """, + ["id rexl", "punct .", "id re", "punct =", "punct {", + "id NAME", "punct :", r"""regex /^(?!\d)(?:\w)+|^"(?:[^"]|"")+"/""", "punct ,", + "id UNQUOTED_LITERAL", "punct :", r"""regex /^@(?:(?!\d)(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/""", "punct ,", + "id QUOTED_LITERAL", "punct :", r"""regex /^'(?:[^']|'')*'/""", "punct ,", + "id NUMERIC_LITERAL", "punct :", r"""regex /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/""", "punct ,", + "id SYMBOL", "punct :", r"""regex /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/""", + "punct }", "punct ;", + "id str", "punct =", """string '"'""", "punct ;", + ]), + + (r""" this._js = "e.str(\"" + this.value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\")"; """, + ["keyword this", "punct .", "id _js", "punct =", r'''string "e.str(\""''', "punct +", "keyword this", "punct .", + "id value", "punct .", "id replace", "punct (", r"regex /\\/g", "punct ,", r'string "\\\\"', "punct )", + "punct .", "id replace", "punct (", r'regex /"/g', "punct ,", r'string "\\\""', "punct )", "punct +", + r'string "\")"', "punct ;"]), + ] + +def make_function(input, toks): + def test_func(self): + lexer = JsLexer() + result = ["%s %s" % (name, tok) for name, tok in lexer.lex(input) if name != 'ws'] + self.assertListEqual(result, toks) + return test_func + +for i, (input, toks) in enumerate(JsTokensTest.LEX_CASES): + setattr(JsTokensTest, "test_case_%d" % i, make_function(input, toks)) + + +GETTEXT_CASES = ( + ( + r""" + a = 1; /* /[0-9]+/ */ + b = 0x2a0b / 1; // /[0-9]+/ + c = 3; + """, + r""" + a = 1; /* /[0-9]+/ */ + b = 0x2a0b / 1; // /[0-9]+/ + c = 3; + """ + ), ( + r""" + a = 1.234e-5; + /* + * /[0-9+/ + */ + b = .0123; + """, + r""" + a = 1.234e-5; + /* + * /[0-9+/ + */ + b = .0123; + """ + ), ( + r""" + x = y / z; + alert(gettext("hello")); + x /= 3; + """, + r""" + x = y / z; + alert(gettext("hello")); + x /= 3; + """ + ), ( + r""" + s = "Hello \"th/foo/ere\""; + s = 'He\x23llo \'th/foo/ere\''; + s = 'slash quote \", just quote "'; + """, + r""" + s = "Hello \"th/foo/ere\""; + s = "He\x23llo \'th/foo/ere\'"; + s = "slash quote \", just quote \""; + """ + ), ( + r""" + s = "Line continuation\ + continued /hello/ still the string";/hello/; + """, + r""" + s = "Line continuation\ + continued /hello/ still the string";"REGEX"; + """ + ), ( + r""" + var regex = /pattern/; + var regex2 = /matter/gm; + var regex3 = /[*/]+/gm.foo("hey"); + """, + r""" + var regex = "REGEX"; + var regex2 = "REGEX"; + var regex3 = "REGEX".foo("hey"); + """ + ), ( + r""" + for (var x = a in foo && "</x>" || mot ? z:/x:3;x<5;y</g/i) {xyz(x++);} + for (var x = a in foo && "</x>" || mot ? z/x:3;x<5;y</g/i) {xyz(x++);} + """, + r""" + for (var x = a in foo && "</x>" || mot ? z:"REGEX"/i) {xyz(x++);} + for (var x = a in foo && "</x>" || mot ? z/x:3;x<5;y<"REGEX") {xyz(x++);} + """ + ), ( + r""" + \u1234xyz = gettext('Hello there'); + """, r""" + Uu1234xyz = gettext("Hello there"); + """ + ) +) + + +class JsToCForGettextTest(TestCase): + pass + +def make_function(js, c): + def test_func(self): + self.assertMultiLineEqual(prepare_js_for_gettext(js), c) + return test_func + +for i, pair in enumerate(GETTEXT_CASES): + setattr(JsToCForGettextTest, "test_case_%d" % i, make_function(*pair)) diff --git a/tests/utils/models.py b/tests/utils/models.py new file mode 100644 index 0000000000..97a72bab14 --- /dev/null +++ b/tests/utils/models.py @@ -0,0 +1 @@ +# Test runner needs a models.py file. diff --git a/tests/utils/module_loading.py b/tests/utils/module_loading.py new file mode 100644 index 0000000000..a9e3706bb9 --- /dev/null +++ b/tests/utils/module_loading.py @@ -0,0 +1,184 @@ +import os +import sys +import imp +from zipimport import zipimporter + +from django.core.exceptions import ImproperlyConfigured +from django.utils import unittest +from django.utils.importlib import import_module +from django.utils.module_loading import import_by_path, module_has_submodule +from django.utils._os import upath + + +class DefaultLoader(unittest.TestCase): + def setUp(self): + sys.meta_path.insert(0, ProxyFinder()) + + def tearDown(self): + sys.meta_path.pop(0) + + def test_loader(self): + "Normal module existence can be tested" + test_module = import_module('regressiontests.utils.test_module') + test_no_submodule = import_module( + 'regressiontests.utils.test_no_submodule') + + # An importable child + self.assertTrue(module_has_submodule(test_module, 'good_module')) + mod = import_module('regressiontests.utils.test_module.good_module') + self.assertEqual(mod.content, 'Good Module') + + # A child that exists, but will generate an import error if loaded + self.assertTrue(module_has_submodule(test_module, 'bad_module')) + self.assertRaises(ImportError, import_module, 'regressiontests.utils.test_module.bad_module') + + # A child that doesn't exist + self.assertFalse(module_has_submodule(test_module, 'no_such_module')) + self.assertRaises(ImportError, import_module, 'regressiontests.utils.test_module.no_such_module') + + # A child that doesn't exist, but is the name of a package on the path + self.assertFalse(module_has_submodule(test_module, 'django')) + self.assertRaises(ImportError, import_module, 'regressiontests.utils.test_module.django') + + # Don't be confused by caching of import misses + import types # causes attempted import of regressiontests.utils.types + self.assertFalse(module_has_submodule(sys.modules['regressiontests.utils'], 'types')) + + # A module which doesn't have a __path__ (so no submodules) + self.assertFalse(module_has_submodule(test_no_submodule, 'anything')) + self.assertRaises(ImportError, import_module, + 'regressiontests.utils.test_no_submodule.anything') + +class EggLoader(unittest.TestCase): + def setUp(self): + self.old_path = sys.path[:] + self.egg_dir = '%s/eggs' % os.path.dirname(upath(__file__)) + + def tearDown(self): + sys.path = self.old_path + sys.path_importer_cache.clear() + + sys.modules.pop('egg_module.sub1.sub2.bad_module', None) + sys.modules.pop('egg_module.sub1.sub2.good_module', None) + sys.modules.pop('egg_module.sub1.sub2', None) + sys.modules.pop('egg_module.sub1', None) + sys.modules.pop('egg_module.bad_module', None) + sys.modules.pop('egg_module.good_module', None) + sys.modules.pop('egg_module', None) + + def test_shallow_loader(self): + "Module existence can be tested inside eggs" + egg_name = '%s/test_egg.egg' % self.egg_dir + sys.path.append(egg_name) + egg_module = import_module('egg_module') + + # An importable child + self.assertTrue(module_has_submodule(egg_module, 'good_module')) + mod = import_module('egg_module.good_module') + self.assertEqual(mod.content, 'Good Module') + + # A child that exists, but will generate an import error if loaded + self.assertTrue(module_has_submodule(egg_module, 'bad_module')) + self.assertRaises(ImportError, import_module, 'egg_module.bad_module') + + # A child that doesn't exist + self.assertFalse(module_has_submodule(egg_module, 'no_such_module')) + self.assertRaises(ImportError, import_module, 'egg_module.no_such_module') + + def test_deep_loader(self): + "Modules deep inside an egg can still be tested for existence" + egg_name = '%s/test_egg.egg' % self.egg_dir + sys.path.append(egg_name) + egg_module = import_module('egg_module.sub1.sub2') + + # An importable child + self.assertTrue(module_has_submodule(egg_module, 'good_module')) + mod = import_module('egg_module.sub1.sub2.good_module') + self.assertEqual(mod.content, 'Deep Good Module') + + # A child that exists, but will generate an import error if loaded + self.assertTrue(module_has_submodule(egg_module, 'bad_module')) + self.assertRaises(ImportError, import_module, 'egg_module.sub1.sub2.bad_module') + + # A child that doesn't exist + self.assertFalse(module_has_submodule(egg_module, 'no_such_module')) + self.assertRaises(ImportError, import_module, 'egg_module.sub1.sub2.no_such_module') + + +class ModuleImportTestCase(unittest.TestCase): + def test_import_by_path(self): + cls = import_by_path( + 'django.utils.module_loading.import_by_path') + self.assertEqual(cls, import_by_path) + + # Test exceptions raised + for path in ('no_dots_in_path', 'unexistent.path', + 'tests.regressiontests.utils.unexistent'): + self.assertRaises(ImproperlyConfigured, import_by_path, path) + + with self.assertRaises(ImproperlyConfigured) as cm: + import_by_path('unexistent.module.path', error_prefix="Foo") + self.assertTrue(str(cm.exception).startswith('Foo')) + + +class ProxyFinder(object): + def __init__(self): + self._cache = {} + + def find_module(self, fullname, path=None): + tail = fullname.rsplit('.', 1)[-1] + try: + fd, fn, info = imp.find_module(tail, path) + if fullname in self._cache: + old_fd = self._cache[fullname][0] + if old_fd: + old_fd.close() + self._cache[fullname] = (fd, fn, info) + except ImportError: + return None + else: + return self # this is a loader as well + + def load_module(self, fullname): + if fullname in sys.modules: + return sys.modules[fullname] + fd, fn, info = self._cache[fullname] + try: + return imp.load_module(fullname, fd, fn, info) + finally: + if fd: + fd.close() + +class TestFinder(object): + def __init__(self, *args, **kwargs): + self.importer = zipimporter(*args, **kwargs) + + def find_module(self, path): + importer = self.importer.find_module(path) + if importer is None: + return + return TestLoader(importer) + +class TestLoader(object): + def __init__(self, importer): + self.importer = importer + + def load_module(self, name): + mod = self.importer.load_module(name) + mod.__loader__ = self + return mod + +class CustomLoader(EggLoader): + """The Custom Loader test is exactly the same as the EggLoader, but + it uses a custom defined Loader and Finder that is intentionally + split into two classes. Although the EggLoader combines both functions + into one class, this isn't required. + """ + def setUp(self): + super(CustomLoader, self).setUp() + sys.path_hooks.insert(0, TestFinder) + sys.path_importer_cache.clear() + + def tearDown(self): + super(CustomLoader, self).tearDown() + sys.path_hooks.pop(0) diff --git a/tests/utils/numberformat.py b/tests/utils/numberformat.py new file mode 100644 index 0000000000..f9d9031e48 --- /dev/null +++ b/tests/utils/numberformat.py @@ -0,0 +1,47 @@ +from unittest import TestCase +from django.utils.numberformat import format as nformat +from sys import float_info + + +class TestNumberFormat(TestCase): + + def test_format_number(self): + self.assertEqual(nformat(1234, '.'), '1234') + self.assertEqual(nformat(1234.2, '.'), '1234.2') + self.assertEqual(nformat(1234, '.', decimal_pos=2), '1234.00') + self.assertEqual(nformat(1234, '.', grouping=2, thousand_sep=','), + '1234') + self.assertEqual(nformat(1234, '.', grouping=2, thousand_sep=',', + force_grouping=True), '12,34') + self.assertEqual(nformat(-1234.33, '.', decimal_pos=1), '-1234.3') + + def test_format_string(self): + self.assertEqual(nformat('1234', '.'), '1234') + self.assertEqual(nformat('1234.2', '.'), '1234.2') + self.assertEqual(nformat('1234', '.', decimal_pos=2), '1234.00') + self.assertEqual(nformat('1234', '.', grouping=2, thousand_sep=','), + '1234') + self.assertEqual(nformat('1234', '.', grouping=2, thousand_sep=',', + force_grouping=True), '12,34') + self.assertEqual(nformat('-1234.33', '.', decimal_pos=1), '-1234.3') + + def test_large_number(self): + most_max = ('{0}179769313486231570814527423731704356798070567525844996' + '598917476803157260780028538760589558632766878171540458953' + '514382464234321326889464182768467546703537516986049910576' + '551282076245490090389328944075868508455133942304583236903' + '222948165808559332123348274797826204144723168738177180919' + '29988125040402618412485836{1}') + most_max2 = ('{0}35953862697246314162905484746340871359614113505168999' + '31978349536063145215600570775211791172655337563430809179' + '07028764928468642653778928365536935093407075033972099821' + '15310256415249098018077865788815173701691026788460916647' + '38064458963316171186642466965495956524082894463374763543' + '61838599762500808052368249716736') + int_max = int(float_info.max) + self.assertEqual(nformat(int_max, '.'), most_max.format('', '8')) + self.assertEqual(nformat(int_max + 1, '.'), most_max.format('', '9')) + self.assertEqual(nformat(int_max * 2, '.'), most_max2.format('')) + self.assertEqual(nformat(0 - int_max, '.'), most_max.format('-', '8')) + self.assertEqual(nformat(-1 - int_max, '.'), most_max.format('-', '9')) + self.assertEqual(nformat(-2 * int_max, '.'), most_max2.format('-')) diff --git a/tests/utils/os_utils.py b/tests/utils/os_utils.py new file mode 100644 index 0000000000..a205d67431 --- /dev/null +++ b/tests/utils/os_utils.py @@ -0,0 +1,26 @@ +import os + +from django.utils import unittest +from django.utils._os import safe_join + + +class SafeJoinTests(unittest.TestCase): + def test_base_path_ends_with_sep(self): + drive, path = os.path.splitdrive(safe_join("/abc/", "abc")) + self.assertEqual( + path, + "{0}abc{0}abc".format(os.path.sep) + ) + + def test_root_path(self): + drive, path = os.path.splitdrive(safe_join("/", "path")) + self.assertEqual( + path, + "{0}path".format(os.path.sep), + ) + + drive, path = os.path.splitdrive(safe_join("/", "")) + self.assertEqual( + path, + os.path.sep, + ) diff --git a/tests/utils/regex_helper.py b/tests/utils/regex_helper.py new file mode 100644 index 0000000000..41f4a4f85d --- /dev/null +++ b/tests/utils/regex_helper.py @@ -0,0 +1,50 @@ +from __future__ import unicode_literals + +from django.utils import regex_helper +from django.utils import unittest + + +class NormalizeTests(unittest.TestCase): + def test_empty(self): + pattern = r"" + expected = [('', [])] + result = regex_helper.normalize(pattern) + self.assertEqual(result, expected) + + def test_escape(self): + pattern = r"\\\^\$\.\|\?\*\+\(\)\[" + expected = [('\\^$.|?*+()[', [])] + result = regex_helper.normalize(pattern) + self.assertEqual(result, expected) + + def test_group_positional(self): + pattern = r"(.*)-(.+)" + expected = [('%(_0)s-%(_1)s', ['_0', '_1'])] + result = regex_helper.normalize(pattern) + self.assertEqual(result, expected) + + def test_group_ignored(self): + pattern = r"(?i)(?L)(?m)(?s)(?u)(?#)" + expected = [('', [])] + result = regex_helper.normalize(pattern) + self.assertEqual(result, expected) + + def test_group_noncapturing(self): + pattern = r"(?:non-capturing)" + expected = [('non-capturing', [])] + result = regex_helper.normalize(pattern) + self.assertEqual(result, expected) + + def test_group_named(self): + pattern = r"(?P<first_group_name>.*)-(?P<second_group_name>.*)" + expected = [('%(first_group_name)s-%(second_group_name)s', + ['first_group_name', 'second_group_name'])] + result = regex_helper.normalize(pattern) + self.assertEqual(result, expected) + + def test_group_backreference(self): + pattern = r"(?P<first_group_name>.*)-(?P=first_group_name)" + expected = [('%(first_group_name)s-%(first_group_name)s', + ['first_group_name'])] + result = regex_helper.normalize(pattern) + self.assertEqual(result, expected) diff --git a/tests/utils/simplelazyobject.py b/tests/utils/simplelazyobject.py new file mode 100644 index 0000000000..3f81e8f608 --- /dev/null +++ b/tests/utils/simplelazyobject.py @@ -0,0 +1,123 @@ +from __future__ import unicode_literals + +import copy +import pickle + +from django.test.utils import str_prefix +from django.utils import six +from django.utils.unittest import TestCase +from django.utils.functional import SimpleLazyObject, empty + + +class _ComplexObject(object): + def __init__(self, name): + self.name = name + + def __eq__(self, other): + return self.name == other.name + + def __hash__(self): + return hash(self.name) + + if six.PY3: + def __bytes__(self): + return ("I am _ComplexObject(%r)" % self.name).encode("utf-8") + + def __str__(self): + return self.name + + else: + def __str__(self): + return b"I am _ComplexObject(%r)" % str(self.name) + + def __unicode__(self): + return self.name + + def __repr__(self): + return "_ComplexObject(%r)" % self.name + + +complex_object = lambda: _ComplexObject("joe") + + +class TestUtilsSimpleLazyObject(TestCase): + """ + Tests for SimpleLazyObject + """ + # Note that concrete use cases for SimpleLazyObject are also found in the + # auth context processor tests (unless the implementation of that function + # is changed). + + def test_equality(self): + self.assertEqual(complex_object(), SimpleLazyObject(complex_object)) + self.assertEqual(SimpleLazyObject(complex_object), complex_object()) + + def test_hash(self): + # hash() equality would not be true for many objects, but it should be + # for _ComplexObject + self.assertEqual(hash(complex_object()), + hash(SimpleLazyObject(complex_object))) + + def test_repr(self): + # For debugging, it will really confuse things if there is no clue that + # SimpleLazyObject is actually a proxy object. So we don't + # proxy __repr__ + self.assertTrue("SimpleLazyObject" in repr(SimpleLazyObject(complex_object))) + + def test_bytes(self): + self.assertEqual(b"I am _ComplexObject('joe')", + bytes(SimpleLazyObject(complex_object))) + + def test_text(self): + self.assertEqual("joe", six.text_type(SimpleLazyObject(complex_object))) + + def test_class(self): + # This is important for classes that use __class__ in things like + # equality tests. + self.assertEqual(_ComplexObject, SimpleLazyObject(complex_object).__class__) + + def test_deepcopy(self): + # Check that we *can* do deep copy, and that it returns the right + # objects. + + # First, for an unevaluated SimpleLazyObject + s = SimpleLazyObject(complex_object) + self.assertIs(s._wrapped, empty) + s2 = copy.deepcopy(s) + # something has gone wrong is s is evaluated + self.assertIs(s._wrapped, empty) + self.assertEqual(s2, complex_object()) + + # Second, for an evaluated SimpleLazyObject + name = s.name # evaluate + self.assertIsNot(s._wrapped, empty) + s3 = copy.deepcopy(s) + self.assertEqual(s3, complex_object()) + + + def test_none(self): + i = [0] + def f(): + i[0] += 1 + return None + + x = SimpleLazyObject(f) + self.assertEqual(str(x), "None") + self.assertEqual(i, [1]) + self.assertEqual(str(x), "None") + self.assertEqual(i, [1]) + + def test_bool(self): + x = SimpleLazyObject(lambda: 3) + self.assertTrue(x) + x = SimpleLazyObject(lambda: 0) + self.assertFalse(x) + + def test_pickle_complex(self): + # See ticket #16563 + x = SimpleLazyObject(complex_object) + pickled = pickle.dumps(x) + unpickled = pickle.loads(pickled) + self.assertEqual(unpickled, x) + self.assertEqual(six.text_type(unpickled), six.text_type(x)) + self.assertEqual(unpickled.name, x.name) diff --git a/tests/utils/termcolors.py b/tests/utils/termcolors.py new file mode 100644 index 0000000000..8cc28009cd --- /dev/null +++ b/tests/utils/termcolors.py @@ -0,0 +1,157 @@ +from django.utils import unittest +from django.utils.termcolors import (parse_color_setting, PALETTES, + DEFAULT_PALETTE, LIGHT_PALETTE, DARK_PALETTE, NOCOLOR_PALETTE, colorize) + + +class TermColorTests(unittest.TestCase): + + def test_empty_string(self): + self.assertEqual(parse_color_setting(''), PALETTES[DEFAULT_PALETTE]) + + def test_simple_palette(self): + self.assertEqual(parse_color_setting('light'), PALETTES[LIGHT_PALETTE]) + self.assertEqual(parse_color_setting('dark'), PALETTES[DARK_PALETTE]) + self.assertEqual(parse_color_setting('nocolor'), None) + + def test_fg(self): + self.assertEqual(parse_color_setting('error=green'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + + def test_fg_bg(self): + self.assertEqual(parse_color_setting('error=green/blue'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'bg':'blue'})) + + def test_fg_opts(self): + self.assertEqual(parse_color_setting('error=green,blink'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'opts': ('blink',)})) + self.assertEqual(parse_color_setting('error=green,bold,blink'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'opts': ('blink','bold')})) + + def test_fg_bg_opts(self): + self.assertEqual(parse_color_setting('error=green/blue,blink'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'bg':'blue', 'opts': ('blink',)})) + self.assertEqual(parse_color_setting('error=green/blue,bold,blink'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'bg':'blue', 'opts': ('blink','bold')})) + + def test_override_palette(self): + self.assertEqual(parse_color_setting('light;error=green'), + dict(PALETTES[LIGHT_PALETTE], + ERROR={'fg':'green'})) + + def test_override_nocolor(self): + self.assertEqual(parse_color_setting('nocolor;error=green'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg': 'green'})) + + def test_reverse_override(self): + self.assertEqual(parse_color_setting('error=green;light'), PALETTES[LIGHT_PALETTE]) + + def test_multiple_roles(self): + self.assertEqual(parse_color_setting('error=green;sql_field=blue'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'}, + SQL_FIELD={'fg':'blue'})) + + def test_override_with_multiple_roles(self): + self.assertEqual(parse_color_setting('light;error=green;sql_field=blue'), + dict(PALETTES[LIGHT_PALETTE], + ERROR={'fg':'green'}, + SQL_FIELD={'fg':'blue'})) + + def test_empty_definition(self): + self.assertEqual(parse_color_setting(';'), None) + self.assertEqual(parse_color_setting('light;'), PALETTES[LIGHT_PALETTE]) + self.assertEqual(parse_color_setting(';;;'), None) + + def test_empty_options(self): + self.assertEqual(parse_color_setting('error=green,'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + self.assertEqual(parse_color_setting('error=green,,,'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + self.assertEqual(parse_color_setting('error=green,,blink,,'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'opts': ('blink',)})) + + def test_bad_palette(self): + self.assertEqual(parse_color_setting('unknown'), None) + + def test_bad_role(self): + self.assertEqual(parse_color_setting('unknown='), None) + self.assertEqual(parse_color_setting('unknown=green'), None) + self.assertEqual(parse_color_setting('unknown=green;sql_field=blue'), + dict(PALETTES[NOCOLOR_PALETTE], + SQL_FIELD={'fg':'blue'})) + + def test_bad_color(self): + self.assertEqual(parse_color_setting('error='), None) + self.assertEqual(parse_color_setting('error=;sql_field=blue'), + dict(PALETTES[NOCOLOR_PALETTE], + SQL_FIELD={'fg':'blue'})) + self.assertEqual(parse_color_setting('error=unknown'), None) + self.assertEqual(parse_color_setting('error=unknown;sql_field=blue'), + dict(PALETTES[NOCOLOR_PALETTE], + SQL_FIELD={'fg':'blue'})) + self.assertEqual(parse_color_setting('error=green/unknown'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + self.assertEqual(parse_color_setting('error=green/blue/something'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'bg': 'blue'})) + self.assertEqual(parse_color_setting('error=green/blue/something,blink'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'bg': 'blue', 'opts': ('blink',)})) + + def test_bad_option(self): + self.assertEqual(parse_color_setting('error=green,unknown'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + self.assertEqual(parse_color_setting('error=green,unknown,blink'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'opts': ('blink',)})) + + def test_role_case(self): + self.assertEqual(parse_color_setting('ERROR=green'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + self.assertEqual(parse_color_setting('eRrOr=green'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + + def test_color_case(self): + self.assertEqual(parse_color_setting('error=GREEN'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + self.assertEqual(parse_color_setting('error=GREEN/BLUE'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'bg':'blue'})) + + self.assertEqual(parse_color_setting('error=gReEn'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green'})) + self.assertEqual(parse_color_setting('error=gReEn/bLuE'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'bg':'blue'})) + + def test_opts_case(self): + self.assertEqual(parse_color_setting('error=green,BLINK'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'opts': ('blink',)})) + + self.assertEqual(parse_color_setting('error=green,bLiNk'), + dict(PALETTES[NOCOLOR_PALETTE], + ERROR={'fg':'green', 'opts': ('blink',)})) + + def test_colorize_empty_text(self): + self.assertEqual(colorize(text=None), '\x1b[m\x1b[0m') + self.assertEqual(colorize(text=''), '\x1b[m\x1b[0m') + + self.assertEqual(colorize(text=None, opts=('noreset')), '\x1b[m') + self.assertEqual(colorize(text='', opts=('noreset')), '\x1b[m') diff --git a/tests/utils/test_module/__init__.py b/tests/utils/test_module/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/utils/test_module/__init__.py diff --git a/tests/utils/test_module/bad_module.py b/tests/utils/test_module/bad_module.py new file mode 100644 index 0000000000..cc0cd168ce --- /dev/null +++ b/tests/utils/test_module/bad_module.py @@ -0,0 +1,3 @@ +import a_package_name_that_does_not_exist + +content = 'Bad Module'
\ No newline at end of file diff --git a/tests/utils/test_module/good_module.py b/tests/utils/test_module/good_module.py new file mode 100644 index 0000000000..0ca689832a --- /dev/null +++ b/tests/utils/test_module/good_module.py @@ -0,0 +1 @@ +content = 'Good Module'
\ No newline at end of file diff --git a/tests/utils/test_no_submodule.py b/tests/utils/test_no_submodule.py new file mode 100644 index 0000000000..3c424ac788 --- /dev/null +++ b/tests/utils/test_no_submodule.py @@ -0,0 +1 @@ +# Used to test for modules which don't have submodules. diff --git a/tests/utils/tests.py b/tests/utils/tests.py new file mode 100644 index 0000000000..726df3d979 --- /dev/null +++ b/tests/utils/tests.py @@ -0,0 +1,33 @@ +""" +Tests for django.utils. +""" +from __future__ import absolute_import + +from .archive import TestBzip2Tar, TestGzipTar, TestTar, TestZip +from .baseconv import TestBaseConv +from .checksums import TestUtilsChecksums +from .crypto import TestUtilsCryptoMisc, TestUtilsCryptoPBKDF2 +from .datastructures import (DictWrapperTests, ImmutableListTests, + MergeDictTests, MultiValueDictTests, SortedDictTests) +from .dateformat import DateFormatTests +from .dateparse import DateParseTests +from .datetime_safe import DatetimeTests +from .decorators import DecoratorFromMiddlewareTests +from .encoding import TestEncodingUtils +from .feedgenerator import FeedgeneratorTest +from .functional import FunctionalTestCase +from .html import TestUtilsHtml +from .http import TestUtilsHttp, ETagProcessingTests, HttpDateProcessingTests +from .ipv6 import TestUtilsIPv6 +from .jslex import JsToCForGettextTest, JsTokensTest +from .module_loading import (CustomLoader, DefaultLoader, EggLoader, + ModuleImportTestCase) +from .numberformat import TestNumberFormat +from .os_utils import SafeJoinTests +from .regex_helper import NormalizeTests +from .simplelazyobject import TestUtilsSimpleLazyObject +from .termcolors import TermColorTests +from .text import TestUtilsText +from .timesince import TimesinceTests +from .timezone import TimezoneTests +from .tzinfo import TzinfoTests diff --git a/tests/utils/text.py b/tests/utils/text.py new file mode 100644 index 0000000000..c9dde6b1b3 --- /dev/null +++ b/tests/utils/text.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import warnings + +from django.test import SimpleTestCase +from django.utils import text + +class TestUtilsText(SimpleTestCase): + + def test_truncate_chars(self): + truncator = text.Truncator( + 'The quick brown fox jumped over the lazy dog.' + ) + self.assertEqual('The quick brown fox jumped over the lazy dog.', + truncator.chars(100)), + self.assertEqual('The quick brown fox ...', + truncator.chars(23)), + self.assertEqual('The quick brown fo.....', + truncator.chars(23, '.....')), + + # Ensure that we normalize our unicode data first + nfc = text.Truncator('o\xfco\xfco\xfco\xfc') + nfd = text.Truncator('ou\u0308ou\u0308ou\u0308ou\u0308') + self.assertEqual('oüoüoüoü', nfc.chars(8)) + self.assertEqual('oüoüoüoü', nfd.chars(8)) + self.assertEqual('oü...', nfc.chars(5)) + self.assertEqual('oü...', nfd.chars(5)) + + # Ensure the final length is calculated correctly when there are + # combining characters with no precomposed form, and that combining + # characters are not split up. + truncator = text.Truncator('-B\u030AB\u030A----8') + self.assertEqual('-B\u030A...', truncator.chars(5)) + self.assertEqual('-B\u030AB\u030A-...', truncator.chars(7)) + self.assertEqual('-B\u030AB\u030A----8', truncator.chars(8)) + + # Ensure the length of the end text is correctly calculated when it + # contains combining characters with no precomposed form. + truncator = text.Truncator('-----') + self.assertEqual('---B\u030A', truncator.chars(4, 'B\u030A')) + self.assertEqual('-----', truncator.chars(5, 'B\u030A')) + + # Make a best effort to shorten to the desired length, but requesting + # a length shorter than the ellipsis shouldn't break + self.assertEqual('...', text.Truncator('asdf').chars(1)) + + def test_truncate_words(self): + truncator = text.Truncator('The quick brown fox jumped over the lazy ' + 'dog.') + self.assertEqual('The quick brown fox jumped over the lazy dog.', + truncator.words(10)) + self.assertEqual('The quick brown fox...', truncator.words(4)) + self.assertEqual('The quick brown fox[snip]', + truncator.words(4, '[snip]')) + + def test_truncate_html_words(self): + truncator = text.Truncator('<p id="par"><strong><em>The quick brown fox' + ' jumped over the lazy dog.</em></strong></p>') + self.assertEqual('<p id="par"><strong><em>The quick brown fox jumped over' + ' the lazy dog.</em></strong></p>', truncator.words(10, html=True)) + self.assertEqual('<p id="par"><strong><em>The quick brown fox...</em>' + '</strong></p>', truncator.words(4, html=True)) + self.assertEqual('<p id="par"><strong><em>The quick brown fox....</em>' + '</strong></p>', truncator.words(4, '....', html=True)) + self.assertEqual('<p id="par"><strong><em>The quick brown fox</em>' + '</strong></p>', truncator.words(4, '', html=True)) + + # Test with new line inside tag + truncator = text.Truncator('<p>The quick <a href="xyz.html"\n' + 'id="mylink">brown fox</a> jumped over the lazy dog.</p>') + self.assertEqual('<p>The quick <a href="xyz.html"\n' + 'id="mylink">brown...</a></p>', truncator.words(3, '...', html=True)) + + # Test self-closing tags + truncator = text.Truncator('<br/>The <hr />quick brown fox jumped over' + ' the lazy dog.') + self.assertEqual('<br/>The <hr />quick brown...', + truncator.words(3, '...', html=True )) + truncator = text.Truncator('<br>The <hr/>quick <em>brown fox</em> ' + 'jumped over the lazy dog.') + self.assertEqual('<br>The <hr/>quick <em>brown...</em>', + truncator.words(3, '...', html=True )) + + def test_wrap(self): + digits = '1234 67 9' + self.assertEqual(text.wrap(digits, 100), '1234 67 9') + self.assertEqual(text.wrap(digits, 9), '1234 67 9') + self.assertEqual(text.wrap(digits, 8), '1234 67\n9') + + self.assertEqual(text.wrap('short\na long line', 7), + 'short\na long\nline') + + self.assertEqual(text.wrap('do-not-break-long-words please? ok', 8), + 'do-not-break-long-words\nplease?\nok') + + long_word = 'l%sng' % ('o' * 20) + self.assertEqual(text.wrap(long_word, 20), long_word) + self.assertEqual(text.wrap('a %s word' % long_word, 10), + 'a\n%s\nword' % long_word) + + def test_slugify(self): + items = ( + ('Hello, World!', 'hello-world'), + ('spam & eggs', 'spam-eggs'), + ) + for value, output in items: + self.assertEqual(text.slugify(value), output) diff --git a/tests/utils/timesince.py b/tests/utils/timesince.py new file mode 100644 index 0000000000..5e641a42c4 --- /dev/null +++ b/tests/utils/timesince.py @@ -0,0 +1,124 @@ +from __future__ import unicode_literals + +import datetime +import unittest + +from django.utils.timesince import timesince, timeuntil +from django.utils.tzinfo import LocalTimezone, FixedOffset + +class TimesinceTests(unittest.TestCase): + + def setUp(self): + self.t = datetime.datetime(2007, 8, 14, 13, 46, 0) + self.onemicrosecond = datetime.timedelta(microseconds=1) + self.onesecond = datetime.timedelta(seconds=1) + self.oneminute = datetime.timedelta(minutes=1) + self.onehour = datetime.timedelta(hours=1) + self.oneday = datetime.timedelta(days=1) + self.oneweek = datetime.timedelta(days=7) + self.onemonth = datetime.timedelta(days=30) + self.oneyear = datetime.timedelta(days=365) + + def test_equal_datetimes(self): + """ equal datetimes. """ + self.assertEqual(timesince(self.t, self.t), '0 minutes') + + def test_ignore_microseconds_and_seconds(self): + """ Microseconds and seconds are ignored. """ + self.assertEqual(timesince(self.t, self.t+self.onemicrosecond), + '0 minutes') + self.assertEqual(timesince(self.t, self.t+self.onesecond), + '0 minutes') + + def test_other_units(self): + """ Test other units. """ + self.assertEqual(timesince(self.t, self.t+self.oneminute), + '1 minute') + self.assertEqual(timesince(self.t, self.t+self.onehour), '1 hour') + self.assertEqual(timesince(self.t, self.t+self.oneday), '1 day') + self.assertEqual(timesince(self.t, self.t+self.oneweek), '1 week') + self.assertEqual(timesince(self.t, self.t+self.onemonth), + '1 month') + self.assertEqual(timesince(self.t, self.t+self.oneyear), '1 year') + + def test_multiple_units(self): + """ Test multiple units. """ + self.assertEqual(timesince(self.t, + self.t+2*self.oneday+6*self.onehour), '2 days, 6 hours') + self.assertEqual(timesince(self.t, + self.t+2*self.oneweek+2*self.oneday), '2 weeks, 2 days') + + def test_display_first_unit(self): + """ + If the two differing units aren't adjacent, only the first unit is + displayed. + """ + self.assertEqual(timesince(self.t, + self.t+2*self.oneweek+3*self.onehour+4*self.oneminute), + '2 weeks') + + self.assertEqual(timesince(self.t, + self.t+4*self.oneday+5*self.oneminute), '4 days') + + def test_display_second_before_first(self): + """ + When the second date occurs before the first, we should always + get 0 minutes. + """ + self.assertEqual(timesince(self.t, self.t-self.onemicrosecond), + '0 minutes') + self.assertEqual(timesince(self.t, self.t-self.onesecond), + '0 minutes') + self.assertEqual(timesince(self.t, self.t-self.oneminute), + '0 minutes') + self.assertEqual(timesince(self.t, self.t-self.onehour), + '0 minutes') + self.assertEqual(timesince(self.t, self.t-self.oneday), + '0 minutes') + self.assertEqual(timesince(self.t, self.t-self.oneweek), + '0 minutes') + self.assertEqual(timesince(self.t, self.t-self.onemonth), + '0 minutes') + self.assertEqual(timesince(self.t, self.t-self.oneyear), + '0 minutes') + self.assertEqual(timesince(self.t, + self.t-2*self.oneday-6*self.onehour), '0 minutes') + self.assertEqual(timesince(self.t, + self.t-2*self.oneweek-2*self.oneday), '0 minutes') + self.assertEqual(timesince(self.t, + self.t-2*self.oneweek-3*self.onehour-4*self.oneminute), + '0 minutes') + self.assertEqual(timesince(self.t, + self.t-4*self.oneday-5*self.oneminute), '0 minutes') + + def test_different_timezones(self): + """ When using two different timezones. """ + now = datetime.datetime.now() + now_tz = datetime.datetime.now(LocalTimezone(now)) + now_tz_i = datetime.datetime.now(FixedOffset((3 * 60) + 15)) + + self.assertEqual(timesince(now), '0 minutes') + self.assertEqual(timesince(now_tz), '0 minutes') + self.assertEqual(timeuntil(now_tz, now_tz_i), '0 minutes') + + def test_date_objects(self): + """ Both timesince and timeuntil should work on date objects (#17937). """ + today = datetime.date.today() + self.assertEqual(timesince(today + self.oneday), '0 minutes') + self.assertEqual(timeuntil(today - self.oneday), '0 minutes') + + def test_both_date_objects(self): + """ Timesince should work with both date objects (#9672) """ + today = datetime.date.today() + self.assertEqual(timeuntil(today + self.oneday, today), '1 day') + self.assertEqual(timeuntil(today - self.oneday, today), '0 minutes') + self.assertEqual(timeuntil(today + self.oneweek, today), '1 week') + + def test_naive_datetime_with_tzinfo_attribute(self): + class naive(datetime.tzinfo): + def utcoffset(self, dt): + return None + future = datetime.datetime(2080, 1, 1, tzinfo=naive()) + self.assertEqual(timesince(future), '0 minutes') + past = datetime.datetime(1980, 1, 1, tzinfo=naive()) + self.assertEqual(timeuntil(past), '0 minutes') diff --git a/tests/utils/timezone.py b/tests/utils/timezone.py new file mode 100644 index 0000000000..650198e8ff --- /dev/null +++ b/tests/utils/timezone.py @@ -0,0 +1,63 @@ +import copy +import datetime +import pickle +from django.test.utils import override_settings +from django.utils import timezone +from django.utils.tzinfo import FixedOffset +from django.utils import unittest + + +EAT = FixedOffset(180) # Africa/Nairobi +ICT = FixedOffset(420) # Asia/Bangkok + + +class TimezoneTests(unittest.TestCase): + + def test_localtime(self): + now = datetime.datetime.utcnow().replace(tzinfo=timezone.utc) + local_tz = timezone.LocalTimezone() + local_now = timezone.localtime(now, local_tz) + self.assertEqual(local_now.tzinfo, local_tz) + + def test_now(self): + with override_settings(USE_TZ=True): + self.assertTrue(timezone.is_aware(timezone.now())) + with override_settings(USE_TZ=False): + self.assertTrue(timezone.is_naive(timezone.now())) + + def test_override(self): + default = timezone.get_default_timezone() + try: + timezone.activate(ICT) + + with timezone.override(EAT): + self.assertIs(EAT, timezone.get_current_timezone()) + self.assertIs(ICT, timezone.get_current_timezone()) + + with timezone.override(None): + self.assertIs(default, timezone.get_current_timezone()) + self.assertIs(ICT, timezone.get_current_timezone()) + + timezone.deactivate() + + with timezone.override(EAT): + self.assertIs(EAT, timezone.get_current_timezone()) + self.assertIs(default, timezone.get_current_timezone()) + + with timezone.override(None): + self.assertIs(default, timezone.get_current_timezone()) + self.assertIs(default, timezone.get_current_timezone()) + finally: + timezone.deactivate() + + def test_copy(self): + self.assertIsInstance(copy.copy(timezone.UTC()), timezone.UTC) + self.assertIsInstance(copy.copy(timezone.LocalTimezone()), timezone.LocalTimezone) + + def test_deepcopy(self): + self.assertIsInstance(copy.deepcopy(timezone.UTC()), timezone.UTC) + self.assertIsInstance(copy.deepcopy(timezone.LocalTimezone()), timezone.LocalTimezone) + + def test_pickling_unpickling(self): + self.assertIsInstance(pickle.loads(pickle.dumps(timezone.UTC())), timezone.UTC) + self.assertIsInstance(pickle.loads(pickle.dumps(timezone.LocalTimezone())), timezone.LocalTimezone) diff --git a/tests/utils/tzinfo.py b/tests/utils/tzinfo.py new file mode 100644 index 0000000000..cec92652cd --- /dev/null +++ b/tests/utils/tzinfo.py @@ -0,0 +1,79 @@ +import copy +import datetime +import os +import pickle +import time +from django.utils.tzinfo import FixedOffset, LocalTimezone +from django.utils import unittest + +class TzinfoTests(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.old_TZ = os.environ.get('TZ') + os.environ['TZ'] = 'US/Eastern' + + try: + # Check if a timezone has been set + time.tzset() + cls.tz_tests = True + except AttributeError: + # No timezone available. Don't run the tests that require a TZ + cls.tz_tests = False + + @classmethod + def tearDownClass(cls): + if cls.old_TZ is None: + del os.environ['TZ'] + else: + os.environ['TZ'] = cls.old_TZ + + # Cleanup - force re-evaluation of TZ environment variable. + if cls.tz_tests: + time.tzset() + + def test_fixedoffset(self): + self.assertEqual(repr(FixedOffset(0)), '+0000') + self.assertEqual(repr(FixedOffset(60)), '+0100') + self.assertEqual(repr(FixedOffset(-60)), '-0100') + self.assertEqual(repr(FixedOffset(280)), '+0440') + self.assertEqual(repr(FixedOffset(-280)), '-0440') + self.assertEqual(repr(FixedOffset(-78.4)), '-0118') + self.assertEqual(repr(FixedOffset(78.4)), '+0118') + self.assertEqual(repr(FixedOffset(-5.5*60)), '-0530') + self.assertEqual(repr(FixedOffset(5.5*60)), '+0530') + self.assertEqual(repr(FixedOffset(-.5*60)), '-0030') + self.assertEqual(repr(FixedOffset(.5*60)), '+0030') + + def test_16899(self): + if not self.tz_tests: + return + ts = 1289106000 + # Midnight at the end of DST in US/Eastern: 2010-11-07T05:00:00Z + dt = datetime.datetime.utcfromtimestamp(ts) + # US/Eastern -- we force its representation to "EST" + tz = LocalTimezone(dt + datetime.timedelta(days=1)) + self.assertEqual( + repr(datetime.datetime.fromtimestamp(ts - 3600, tz)), + 'datetime.datetime(2010, 11, 7, 0, 0, tzinfo=EST)') + self.assertEqual( + repr(datetime.datetime.fromtimestamp(ts, tz)), + 'datetime.datetime(2010, 11, 7, 1, 0, tzinfo=EST)') + self.assertEqual( + repr(datetime.datetime.fromtimestamp(ts + 3600, tz)), + 'datetime.datetime(2010, 11, 7, 1, 0, tzinfo=EST)') + + def test_copy(self): + now = datetime.datetime.now() + self.assertIsInstance(copy.copy(FixedOffset(90)), FixedOffset) + self.assertIsInstance(copy.copy(LocalTimezone(now)), LocalTimezone) + + def test_deepcopy(self): + now = datetime.datetime.now() + self.assertIsInstance(copy.deepcopy(FixedOffset(90)), FixedOffset) + self.assertIsInstance(copy.deepcopy(LocalTimezone(now)), LocalTimezone) + + def test_pickling_unpickling(self): + now = datetime.datetime.now() + self.assertIsInstance(pickle.loads(pickle.dumps(FixedOffset(90))), FixedOffset) + self.assertIsInstance(pickle.loads(pickle.dumps(LocalTimezone(now))), LocalTimezone) |
