From c45d8722db0149919be762731278bec352422b21 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 25 Nov 2011 17:01:27 +0100 Subject: [PATCH] Introduced a `get_hashed_content` function and actually enabled the tests for the CSS hash method tests. --- compressor/cache.py | 13 ++++ compressor/filters/css_default.py | 11 ++-- docs/changelog.txt | 4 ++ docs/settings.txt | 4 +- tests/tests/__init__.py | 10 ++- tests/tests/filters.py | 102 +++++++++++++++--------------- 6 files changed, 82 insertions(+), 62 deletions(-) diff --git a/compressor/cache.py b/compressor/cache.py index 9db3625..62986b6 100644 --- a/compressor/cache.py +++ b/compressor/cache.py @@ -101,6 +101,19 @@ def get_hashed_mtime(filename, length=12): return get_hexdigest(mtime, length) +def get_hashed_content(filename, length=12): + try: + filename = os.path.realpath(filename) + except OSError: + return None + hash_file = open(filename) + try: + content = hash_file.read() + finally: + hash_file.close() + return get_hexdigest(content, length) + + def cache_get(key): packed_val = cache.get(key) if packed_val is None: diff --git a/compressor/filters/css_default.py b/compressor/filters/css_default.py index 3cba152..8a05f87 100644 --- a/compressor/filters/css_default.py +++ b/compressor/filters/css_default.py @@ -2,7 +2,8 @@ import os import re import posixpath -from compressor.cache import get_hexdigest, get_hashed_mtime +from compressor.cache import (get_hexdigest, get_hashed_mtime, + get_hashed_content) from compressor.conf import settings from compressor.filters import FilterBase, FilterError from compressor.utils import staticfiles @@ -61,12 +62,8 @@ class CssAbsoluteFilter(FilterBase): if filename: if settings.COMPRESS_CSS_HASHING_METHOD == "mtime": suffix = get_hashed_mtime(filename) - elif settings.COMPRESS_CSS_HASHING_METHOD == "hash": - hash_file = open(filename) - try: - suffix = get_hexdigest(hash_file.read(), 12) - finally: - hash_file.close() + elif settings.COMPRESS_CSS_HASHING_METHOD in ("hash", "content"): + suffix = get_hashed_content(filename) else: raise FilterError('COMPRESS_CSS_HASHING_METHOD is configured ' 'with an unknown method (%s).') diff --git a/docs/changelog.txt b/docs/changelog.txt index a0b8f75..d67639f 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -45,6 +45,10 @@ v1.2.0 - Correctly handle offline compressing files that are found in ``{% if %}`` template blocks. +- Renamed the second option for the ``COMPRESS_CSS_HASHING_METHOD`` setting + from ``'hash'`` to ``'content'`` to better describe what it does. The old + name is also supported, as well as the default being ``'mtime'``. + v1.1.1 ------ diff --git a/docs/settings.txt b/docs/settings.txt index 7bf98fe..db39c57 100644 --- a/docs/settings.txt +++ b/docs/settings.txt @@ -97,8 +97,8 @@ A filter that normalizes the URLs used in ``url()`` CSS statements. - ``COMPRESS_CSS_HASHING_METHOD`` -- The method to use when calculating the hash to append to processed URLs. Either ``'mtime'`` (default) or - ``'hash'``. Use the latter in case you're using multiple server to serve - your static files. + ``'content'``. Use the latter in case you're using multiple server to + serve your static files. ``compressor.filters.csstidy.CSSTidyFilter`` """""""""""""""""""""""""""""""""""""""""""" diff --git a/tests/tests/__init__.py b/tests/tests/__init__.py index 7212692..dfaadab 100644 --- a/tests/tests/__init__.py +++ b/tests/tests/__init__.py @@ -1,8 +1,12 @@ -from .base import CompressorTestCase, CssMediaTestCase, VerboseTestCase, CacheBackendTestCase -from .filters import CssTidyTestCase, PrecompilerTestCase, CssMinTestCase, CssAbsolutizingTestCase, CssDataUriTestCase +from .base import (CompressorTestCase, CssMediaTestCase, VerboseTestCase, + CacheBackendTestCase) +from .filters import (CssTidyTestCase, PrecompilerTestCase, CssMinTestCase, + CssAbsolutizingTestCase, CssAbsolutizingTestCaseWithHash, + CssDataUriTestCase) from .jinja2ext import TestJinja2CompressorExtension from .offline import OfflineGenerationTestCase -from .parsers import LxmlParserTests, Html5LibParserTests, BeautifulSoupParserTests, HtmlParserTests +from .parsers import (LxmlParserTests, Html5LibParserTests, + BeautifulSoupParserTests, HtmlParserTests) from .signals import PostCompressSignalTestCase from .storages import StorageTestCase from .templatetags import TemplatetagTestCase, PrecompilerTemplatetagTestCase diff --git a/tests/tests/filters.py b/tests/tests/filters.py index 1645fa6..abf74ed 100644 --- a/tests/tests/filters.py +++ b/tests/tests/filters.py @@ -5,14 +5,15 @@ from unittest2 import skipIf from django.test import TestCase -from compressor.cache import get_hashed_mtime +from compressor.cache import get_hashed_mtime, get_hashed_content from compressor.conf import settings from compressor.css import CssCompressor from compressor.utils import find_command from compressor.filters.base import CompilerFilter +from compressor.filters.cssmin import CSSMinFilter +from compressor.filters.css_default import CssAbsoluteFilter -from .templatetags import render -from .base import css_tag, test_dir +from .base import test_dir class CssTidyTestCase(TestCase): @@ -39,7 +40,7 @@ class PrecompilerTestCase(TestCase): self.filename = os.path.join(test_dir, 'media/css/one.css') with open(self.filename) as f: self.content = f.read() - self.test_precompiler = os.path.join(test_dir, 'precompiler.py') + self.test_precompiler = os.path.join(test_dir, 'precompiler.py') def test_precompiler_infile_outfile(self): command = '%s %s -f {infile} -o {outfile}' % (sys.executable, self.test_precompiler) @@ -47,29 +48,28 @@ class PrecompilerTestCase(TestCase): self.assertEqual(u"body { color:#990; }", compiler.input()) def test_precompiler_infile_stdout(self): - command = '%s %s -f {infile}' % (sys.executable, self.test_precompiler) + command = '%s %s -f {infile}' % (sys.executable, self.test_precompiler) compiler = CompilerFilter(content=self.content, filename=None, command=command) self.assertEqual(u"body { color:#990; }%s" % os.linesep, compiler.input()) def test_precompiler_stdin_outfile(self): - command = '%s %s -o {outfile}' % (sys.executable, self.test_precompiler) + command = '%s %s -o {outfile}' % (sys.executable, self.test_precompiler) compiler = CompilerFilter(content=self.content, filename=None, command=command) self.assertEqual(u"body { color:#990; }", compiler.input()) def test_precompiler_stdin_stdout(self): - command = '%s %s' % (sys.executable, self.test_precompiler) + command = '%s %s' % (sys.executable, self.test_precompiler) compiler = CompilerFilter(content=self.content, filename=None, command=command) self.assertEqual(u"body { color:#990; }%s" % os.linesep, compiler.input()) def test_precompiler_stdin_stdout_filename(self): - command = '%s %s' % (sys.executable, self.test_precompiler) + command = '%s %s' % (sys.executable, self.test_precompiler) compiler = CompilerFilter(content=self.content, filename=self.filename, command=command) self.assertEqual(u"body { color:#990; }%s" % os.linesep, compiler.input()) class CssMinTestCase(TestCase): def test_cssmin_filter(self): - from compressor.filters.cssmin import CSSMinFilter content = """p { @@ -78,78 +78,88 @@ class CssMinTestCase(TestCase): } """ - output = "p{background:#369 url('../../images/image.gif')}" + output = "p{background:#369 url('../../images/image.gif')}" self.assertEqual(output, CSSMinFilter(content).output()) class CssAbsolutizingTestCase(TestCase): + hashing_method = 'mtime' + hashing_func = staticmethod(get_hashed_mtime) + content = "p { background: url('../../img/python.png') }" + def setUp(self): + self.old_enabled = settings.COMPRESS_ENABLED + self.old_url = settings.COMPRESS_URL + self.old_hashing_method = settings.COMPRESS_CSS_HASHING_METHOD settings.COMPRESS_ENABLED = True settings.COMPRESS_URL = '/media/' - settings.COMPRESS_CSS_HASHING_METHOD = 'mtime' + settings.COMPRESS_CSS_HASHING_METHOD = self.hashing_method self.css = """ """ self.css_node = CssCompressor(self.css) - def suffix_method(self, filename): - return get_hashed_mtime(filename) + def tearDown(self): + settings.COMPRESS_ENABLED = self.old_enabled + settings.COMPRESS_URL = self.old_url + settings.COMPRESS_CSS_HASHING_METHOD = self.old_hashing_method def test_css_absolute_filter(self): - from compressor.filters.css_default import CssAbsoluteFilter filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css') imagefilename = os.path.join(settings.COMPRESS_ROOT, 'img/python.png') - content = "p { background: url('../../img/python.png') }" - output = "p { background: url('%simg/python.png?%s') }" % (settings.COMPRESS_URL, self.suffix_method(imagefilename)) - filter = CssAbsoluteFilter(content) + output = "p { background: url('%simg/python.png?%s') }" % (settings.COMPRESS_URL, self.hashing_func(imagefilename)) + filter = CssAbsoluteFilter(self.content) self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css')) settings.COMPRESS_URL = 'http://media.example.com/' - filter = CssAbsoluteFilter(content) + filter = CssAbsoluteFilter(self.content) filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css') - output = "p { background: url('%simg/python.png?%s') }" % (settings.COMPRESS_URL, self.suffix_method(imagefilename)) + output = "p { background: url('%simg/python.png?%s') }" % (settings.COMPRESS_URL, self.hashing_func(imagefilename)) self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css')) def test_css_absolute_filter_https(self): - from compressor.filters.css_default import CssAbsoluteFilter filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css') imagefilename = os.path.join(settings.COMPRESS_ROOT, 'img/python.png') - content = "p { background: url('../../img/python.png') }" - output = "p { background: url('%simg/python.png?%s') }" % (settings.COMPRESS_URL, self.suffix_method(imagefilename)) - filter = CssAbsoluteFilter(content) + output = "p { background: url('%simg/python.png?%s') }" % (settings.COMPRESS_URL, self.hashing_func(imagefilename)) + filter = CssAbsoluteFilter(self.content) self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css')) settings.COMPRESS_URL = 'https://media.example.com/' - filter = CssAbsoluteFilter(content) + filter = CssAbsoluteFilter(self.content) filename = os.path.join(settings.COMPRESS_ROOT, 'css/url/test.css') - output = "p { background: url('%simg/python.png?%s') }" % (settings.COMPRESS_URL, self.suffix_method(imagefilename)) + output = "p { background: url('%simg/python.png?%s') }" % (settings.COMPRESS_URL, self.hashing_func(imagefilename)) self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css')) def test_css_absolute_filter_relative_path(self): - from compressor.filters.css_default import CssAbsoluteFilter filename = os.path.join(settings.TEST_DIR, 'whatever', '..', 'media', 'whatever/../css/url/test.css') imagefilename = os.path.join(settings.COMPRESS_ROOT, 'img/python.png') - content = "p { background: url('../../img/python.png') }" - output = "p { background: url('%simg/python.png?%s') }" % (settings.COMPRESS_URL, self.suffix_method(imagefilename)) - filter = CssAbsoluteFilter(content) + output = "p { background: url('%simg/python.png?%s') }" % (settings.COMPRESS_URL, self.hashing_func(imagefilename)) + filter = CssAbsoluteFilter(self.content) self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css')) settings.COMPRESS_URL = 'https://media.example.com/' - filter = CssAbsoluteFilter(content) - output = "p { background: url('%simg/python.png?%s') }" % (settings.COMPRESS_URL, self.suffix_method(imagefilename)) + filter = CssAbsoluteFilter(self.content) + output = "p { background: url('%simg/python.png?%s') }" % (settings.COMPRESS_URL, self.hashing_func(imagefilename)) self.assertEqual(output, filter.input(filename=filename, basename='css/url/test.css')) def test_css_hunks(self): hash_dict = { - 'hash1': self.suffix_method(os.path.join(settings.COMPRESS_ROOT, 'img/python.png')), - 'hash2': self.suffix_method(os.path.join(settings.COMPRESS_ROOT, 'img/add.png')), + 'hash1': self.hashing_func(os.path.join(settings.COMPRESS_ROOT, 'img/python.png')), + 'hash2': self.hashing_func(os.path.join(settings.COMPRESS_ROOT, 'img/add.png')), } - out = [u"p { background: url('/media/img/python.png?%(hash1)s'); }\np { background: url('/media/img/python.png?%(hash1)s'); }\np { background: url('/media/img/python.png?%(hash1)s'); }\np { background: url('/media/img/python.png?%(hash1)s'); }\n" % hash_dict, - u"p { background: url('/media/img/add.png?%(hash2)s'); }\np { background: url('/media/img/add.png?%(hash2)s'); }\np { background: url('/media/img/add.png?%(hash2)s'); }\np { background: url('/media/img/add.png?%(hash2)s'); }\n" % hash_dict] hunks = [h for m, h in self.css_node.hunks()] - self.assertEqual(out, hunks) + self.assertEqual([u"""\ +p { background: url('/media/img/python.png?%(hash1)s'); } +p { background: url('/media/img/python.png?%(hash1)s'); } +p { background: url('/media/img/python.png?%(hash1)s'); } +p { background: url('/media/img/python.png?%(hash1)s'); } +""" % hash_dict, + u"""\ +p { background: url('/media/img/add.png?%(hash2)s'); } +p { background: url('/media/img/add.png?%(hash2)s'); } +p { background: url('/media/img/add.png?%(hash2)s'); } +p { background: url('/media/img/add.png?%(hash2)s'); } +""" % hash_dict], hunks) def test_guess_filename(self): - import urllib - from compressor.filters.css_default import CssAbsoluteFilter for base_url in ('/media/', 'http://media.example.com/'): settings.COMPRESS_URL = base_url url = '%s/img/python.png' % settings.COMPRESS_URL.rstrip('/') @@ -158,24 +168,19 @@ class CssAbsolutizingTestCase(TestCase): filter = CssAbsoluteFilter(content) self.assertEqual(path, filter.guess_filename(url)) + class CssAbsolutizingTestCaseWithHash(CssAbsolutizingTestCase): + hashing_method = 'content' + hashing_func = staticmethod(get_hashed_content) def setUp(self): - settings.COMPRESS_ENABLED = True - settings.COMPRESS_URL = '/media/' - settings.COMPRESS_CSS_HASHING_METHOD = 'hash' + super(CssAbsolutizingTestCaseWithHash, self).setUp() self.css = """ """ self.css_node = CssCompressor(self.css) - def suffix_method(self, filename): - f = open(filename) - suffix = "H%s" % (get_hexdigest(f.read(), 12), ) - f.close() - return suffix - class CssDataUriTestCase(TestCase): def setUp(self): @@ -196,6 +201,3 @@ class CssDataUriTestCase(TestCase): out = [u'.add { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJvSURBVDjLpZPrS5NhGIf9W7YvBYOkhlkoqCklWChv2WyKik7blnNris72bi6dus0DLZ0TDxW1odtopDs4D8MDZuLU0kXq61CijSIIasOvv94VTUfLiB74fXngup7nvrnvJABJ/5PfLnTTdcwOj4RsdYmo5glBWP6iOtzwvIKSWstI0Wgx80SBblpKtE9KQs/We7EaWoT/8wbWP61gMmCH0lMDvokT4j25TiQU/ITFkek9Ow6+7WH2gwsmahCPdwyw75uw9HEO2gUZSkfyI9zBPCJOoJ2SMmg46N61YO/rNoa39Xi41oFuXysMfh36/Fp0b7bAfWAH6RGi0HglWNCbzYgJaFjRv6zGuy+b9It96N3SQvNKiV9HvSaDfFEIxXItnPs23BzJQd6DDEVM0OKsoVwBG/1VMzpXVWhbkUM2K4oJBDYuGmbKIJ0qxsAbHfRLzbjcnUbFBIpx/qH3vQv9b3U03IQ/HfFkERTzfFj8w8jSpR7GBE123uFEYAzaDRIqX/2JAtJbDat/COkd7CNBva2cMvq0MGxp0PRSCPF8BXjWG3FgNHc9XPT71Ojy3sMFdfJRCeKxEsVtKwFHwALZfCUk3tIfNR8XiJwc1LmL4dg141JPKtj3WUdNFJqLGFVPC4OkR4BxajTWsChY64wmCnMxsWPCHcutKBxMVp5mxA1S+aMComToaqTRUQknLTH62kHOVEE+VQnjahscNCy0cMBWsSI0TCQcZc5ALkEYckL5A5noWSBhfm2AecMAjbcRWV0pUTh0HE64TNf0mczcnnQyu/MilaFJCae1nw2fbz1DnVOxyGTlKeZft/Ff8x1BRssfACjTwQAAAABJRU5ErkJggg=="); }\n.python { background-image: url("/media/img/python.png?%s"); }\n.datauri { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IAAAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1JREFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jqch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0 vr4MkhoXe0rZigAAAABJRU5ErkJggg=="); }\n' % datauri_hash] hunks = [h for m, h in self.css_node.hunks()] self.assertEqual(out, hunks) - - -