Fold jingo_minify into our code.

This will simplify the upgrade to Django 1.11 and allow us to keep this
under control, upstream isn't properly maintained.

* Cleanup jingo-minify, keep only parts we are using. Cleanup tests

Also fix and simplify compress_assets to link to the correct /static/ folder.

Originally we hardcoded to look for settings.STATIC_ROOT but that
doesn't suffice since it's set to /site-static/ by default.

We are now using hardcoding the source of our static files instead of
guessing and mistakenly re-using STATIC_ROOT.

This commit also removes verbosity as an argument and simply logs all
errors immediately.

To simplify testing it adds a --force parameter.

To verify this works locally:

* Change settings.STATIC_ROOT to `'/site-static/`'
* Add `/site-static/` to the folders served by nginx (I'll open an issue
on our nginx container to serve this by default too)
* You may have to restart the nginx container after that for nginx to
pick things up
* Remove everything inside site-static folder
* Run `make update_assets`

Fixes #8532
This commit is contained in:
Christopher Grebs 2018-06-28 15:27:34 +02:00
Родитель 1000aacd45
Коммит 1bf79b82fa
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: D7BCDE311BFC58DD
11 изменённых файлов: 590 добавлений и 110 удалений

Просмотреть файл

@ -91,7 +91,7 @@ RUN DJANGO_SETTINGS_MODULE='settings_local' locale/compile-mo.sh locale
# compile asssets
RUN npm install \
&& make -f Makefile-docker copy_node_js \
&& DJANGO_SETTINGS_MODULE='settings_local' python manage.py compress_assets --use-uuid -t \
&& DJANGO_SETTINGS_MODULE='settings_local' python manage.py compress_assets \
&& DJANGO_SETTINGS_MODULE='settings_local' python manage.py collectstatic --noinput
RUN rm -f settings_local.py settings_local.pyc

Просмотреть файл

@ -130,7 +130,7 @@ update_db:
schematic src/olympia/migrations
update_assets:
python manage.py compress_assets --use-uuid
python manage.py compress_assets
python manage.py collectstatic --noinput
update: update_deps update_db update_assets

Просмотреть файл

@ -60,21 +60,6 @@ def mock_basket(settings):
json={'status': 'ok', 'token': USER_TOKEN})
@pytest.fixture(autouse=True)
def mock_inline_css(monkeypatch):
"""Mock jingo_minify.helpers.is_external: don't break on missing files.
When testing, we don't want nor need the bundled/minified css files, so
pretend that all the css files are external.
Mocking this will prevent amo.templatetags.jinja_helpers.inline_css to
believe it should bundle the css.
"""
from olympia.amo.templatetags import jinja_helpers
monkeypatch.setattr(jinja_helpers, 'is_external', lambda css: True)
def pytest_configure(config):
from olympia.amo.tests import prefix_indexes
prefix_indexes(config)
@ -118,6 +103,7 @@ def test_pre_setup(request, tmpdir, settings):
settings.MEDIA_ROOT = str(tmpdir.mkdir('media'))
settings.TMP_PATH = str(tmpdir.mkdir('tmp'))
settings.STATIC_ROOT = str(tmpdir.mkdir('site-static'))
settings.NETAPP_STORAGE = settings.TMP_PATH
# Reset the prefixer and urlconf after updating media root

Просмотреть файл

@ -217,9 +217,6 @@ isodate==0.6.0 \
# jmespath is required by boto3
jmespath==0.9.3 \
--hash=sha256:f11b4461f425740a1d908e9a3f7365c3d2e569f6ca68a2ff8bc5bcd9676edd63
jingo_minify==0.7.0 \
--hash=sha256:1427946e8d4082c909a98dbacfe1cd908cbdc83572003e1a0236da02b350e31f \
--hash=sha256:f47ec7868467a1b270e115c301a4270137836ddb7b5f57552e65dff5c86aee05
# kombu is required by celery
kombu==4.2.1 \
--hash=sha256:b274db3a4eacc4789aeb24e1de3e460586db7c4fc8610f7adcc7a3a1709a60af \

Просмотреть файл

@ -0,0 +1,232 @@
import hashlib
import os
import re
import time
import uuid
import subprocess
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
from django.contrib.staticfiles.finders import find as find_static_path
from olympia.lib.jingo_minify_helpers import ensure_path_exists
def run_command(command):
"""Run a command and correctly poll the output and write that to stdout"""
process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True)
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
break
if output:
print(output.strip())
return process.poll()
class Command(BaseCommand):
help = ('Compresses css and js assets defined in settings.MINIFY_BUNDLES')
requires_model_validation = False
checked_hash = {}
bundle_hashes = {}
missing_files = 0
minify_skipped = 0
def add_arguments(self, parser):
"""Handle command arguments."""
parser.add_argument(
'force', action='store_true',
help='Ignores modified/created dates and forces compression.')
def generate_build_id(self):
return uuid.uuid4().hex[:8]
def update_hashes(self):
# Adds a time based hash on to the build id.
self.build_id = '%s-%s' % (
self.generate_build_id(), hex(int(time.time()))[2:])
build_id_file = os.path.realpath(
os.path.join(settings.ROOT, 'build.py'))
with open(build_id_file, 'w') as f:
f.write('BUILD_ID_CSS = "%s"\n' % self.build_id)
f.write('BUILD_ID_JS = "%s"\n' % self.build_id)
f.write('BUILD_ID_IMG = "%s"\n' % self.build_id)
f.write('BUNDLE_HASHES = %s\n' % self.bundle_hashes)
def handle(self, **options):
self.force_compress = options.get('force', False)
# This will loop through every bundle, and do the following:
# - Concat all files into one
# - Cache bust all images in CSS files
# - Minify the concatted files
for ftype, bundle in settings.MINIFY_BUNDLES.iteritems():
for name, files in bundle.iteritems():
# Set the paths to the files.
concatted_file = os.path.join(
settings.ROOT, 'static',
ftype, '%s-all.%s' % (name, ftype,))
compressed_file = os.path.join(
settings.ROOT, 'static',
ftype, '%s-min.%s' % (name, ftype,))
ensure_path_exists(concatted_file)
ensure_path_exists(compressed_file)
files_all = []
for fn in files:
processed = self._preprocess_file(fn)
# If the file can't be processed, we skip it.
if processed is not None:
files_all.append(processed)
# Concat all the files.
tmp_concatted = '%s.tmp' % concatted_file
if len(files_all) == 0:
raise CommandError(
'No input files specified in '
'MINIFY_BUNDLES["%s"]["%s"] in settings.py!' %
(ftype, name)
)
run_command('cat {files} > {tmp}'.format(
files=' '.join(files_all),
tmp=tmp_concatted
))
# Cache bust individual images in the CSS.
if ftype == 'css':
bundle_hash = self._cachebust(tmp_concatted, name)
self.bundle_hashes['%s:%s' % (ftype, name)] = bundle_hash
# Compresses the concatenations.
is_changed = self._is_changed(concatted_file)
self._clean_tmp(concatted_file)
if is_changed or not os.path.isfile(compressed_file):
self._minify(ftype, concatted_file, compressed_file)
else:
print(
'File unchanged, skipping minification of %s' % (
concatted_file))
self.minify_skipped += 1
# Write out the hashes
self.update_hashes()
if self.minify_skipped:
print(
'Unchanged files skipped for minification: %s' % (
self.minify_skipped))
def _preprocess_file(self, filename):
"""Preprocess files and return new filenames."""
css_bin = filename.endswith('.less') and settings.LESS_BIN
source = find_static_path(filename)
target = source
if css_bin:
target = '%s.css' % source
run_command('{lessc} {source} {target}'.format(
lessc=css_bin,
source=str(source),
target=str(target)))
return target
def _is_changed(self, concatted_file):
"""Check if the file has been changed."""
if self.force_compress:
return True
tmp_concatted = '%s.tmp' % concatted_file
file_exists = (
os.path.exists(concatted_file) and
os.path.getsize(concatted_file) == os.path.getsize(tmp_concatted))
if file_exists:
orig_hash = self._file_hash(concatted_file)
temp_hash = self._file_hash(tmp_concatted)
return orig_hash != temp_hash
return True # Different filesize, so it was definitely changed
def _clean_tmp(self, concatted_file):
"""Replace the old file with the temp file."""
tmp_concatted = '%s.tmp' % concatted_file
if os.path.exists(concatted_file):
os.remove(concatted_file)
os.rename(tmp_concatted, concatted_file)
def _cachebust(self, css_file, bundle_name):
"""Cache bust images. Return a new bundle hash."""
print('Cache busting images in %s' % re.sub('.tmp$', '', css_file))
if not os.path.exists(css_file):
return
css_content = ''
with open(css_file, 'r') as css_in:
css_content = css_in.read()
def _parse(url):
return self._cachebust_regex(url, css_file)
css_parsed = re.sub('url\(([^)]*?)\)', _parse, css_content)
with open(css_file, 'w') as css_out:
css_out.write(css_parsed)
# Return bundle hash for cachebusting JS/CSS files.
file_hash = hashlib.md5(css_parsed).hexdigest()[0:7]
self.checked_hash[css_file] = file_hash
if self.missing_files:
print(' - Error finding %s images' % (self.missing_files,))
self.missing_files = 0
return file_hash
def _minify(self, ftype, file_in, file_out):
"""Run the proper minifier on the file."""
if ftype == 'js' and hasattr(settings, 'UGLIFY_BIN'):
opts = {'method': 'UglifyJS', 'bin': settings.UGLIFY_BIN}
run_command('{uglify} -v -o {target} {source} -m'.format(
uglify=opts['bin'],
target=file_out,
source=file_in))
elif ftype == 'css' and hasattr(settings, 'CLEANCSS_BIN'):
opts = {'method': 'clean-css', 'bin': settings.CLEANCSS_BIN}
run_command('{cleancss} -o {target} {source}'.format(
cleancss=opts['bin'],
target=file_out,
source=file_in))
print('Minifying %s (using %s)' % (file_in, opts['method']))
def _file_hash(self, url):
"""Open the file and get a hash of it."""
if url in self.checked_hash:
return self.checked_hash[url]
file_hash = ''
try:
with open(url) as f:
file_hash = hashlib.md5(f.read()).hexdigest()[0:7]
except IOError:
self.missing_files += 1
print(' - Could not find file %s' % url)
self.checked_hash[url] = file_hash
return file_hash
def _cachebust_regex(self, img, parent):
"""Run over the regex; img is the structural regex object."""
url = img.group(1).strip('"\'')
if url.startswith('data:') or url.startswith('http'):
return 'url(%s)' % url
url = url.split('?')[0]
full_url = os.path.join(
settings.ROOT, os.path.dirname(parent), url)
return 'url(%s?%s)' % (url, self._file_hash(full_url))

Просмотреть файл

@ -2,7 +2,6 @@ import collections
import json as jsonlib
import os
import random
import re
from operator import attrgetter
from urlparse import urljoin
@ -29,8 +28,7 @@ from olympia import amo
from olympia.amo import urlresolvers, utils
from olympia.constants.licenses import PERSONA_LICENSES_IDS
from olympia.lib.jingo_minify_helpers import (
_build_html, _get_compiled_css_url, get_css_urls, get_js_urls, get_path,
is_external)
_build_html, get_css_urls, get_js_urls)
from olympia.lib.cache import cache_get_or_set, make_key
@ -482,45 +480,6 @@ def _relative_to_absolute(url):
return 'url(%s)' % url
@library.global_function
def inline_css(bundle, media=False, debug=None):
"""
If we are in debug mode, just output a single style tag for each css file.
If we are not in debug mode, return a style that contains bundle-min.css.
Forces a regular css() call for external URLs (no inline allowed).
Extracted from jingo-minify and re-registered, see:
https://github.com/jsocol/jingo-minify/pull/41
Added: turns relative links to absolute ones using STATIC_URL.
"""
if debug is None:
debug = getattr(settings, 'DEBUG', False)
if debug:
items = [_get_compiled_css_url(i)
for i in settings.MINIFY_BUNDLES['css'][bundle]]
else:
items = ['css/%s-min.css' % bundle]
if not media:
media = getattr(settings, 'CSS_MEDIA_DEFAULT', 'screen,projection,tv')
contents = []
for css in items:
if is_external(css):
return _build_html([css], '<link rel="stylesheet" media="%s" '
'href="%%s" />' % media)
with open(get_path(css), 'r') as f:
css_content = f.read()
css_parsed = re.sub(r'url\(([^)]*?)\)',
_relative_to_absolute,
css_content)
contents.append(css_parsed)
return _build_html(contents, '<style type="text/css" media="%s">%%s'
'</style>' % media)
# A (temporary?) copy of this is in services/utils.py. See bug 1055654.
def user_media_path(what):
"""Make it possible to override storage paths in settings.

Просмотреть файл

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import os
from importlib import import_module
from django.conf import settings
@ -43,3 +44,59 @@ def test_cron_jobs_setting():
for name, path in settings.CRON_JOBS.iteritems():
module = import_module(path)
getattr(module, name)
@pytest.mark.static_assets
def test_compress_assets_command_without_git():
settings.MINIFY_BUNDLES = {
'css': {'zamboni/css': ['css/legacy/main.css']}}
call_command('compress_assets')
build_id_file = os.path.realpath(os.path.join(settings.ROOT, 'build.py'))
assert os.path.exists(build_id_file)
with open(build_id_file) as f:
contents_before = f.read()
# Call command a second time. We should get a different build id, since it
# depends on a uuid.
call_command('compress_assets')
with open(build_id_file) as f:
contents_after = f.read()
assert contents_before != contents_after
@pytest.mark.static_assets
def test_compress_assets_correctly_fetches_static_images(settings, tmpdir):
"""
Make sure that `compress_assets` correctly fetches static assets
such as icons and writes them correctly into our compressed
and concatted files.
Refs https://github.com/mozilla/addons-server/issues/8760
"""
settings.MINIFY_BUNDLES = {
'css': {'zamboni/css': ['css/legacy/main.css']}}
# Now run compress and collectstatic
call_command('compress_assets', force=True)
call_command('collectstatic', interactive=False)
css_all = os.path.join(
settings.STATIC_ROOT, 'css', 'zamboni', 'css-all.css')
css_min = os.path.join(
settings.STATIC_ROOT, 'css', 'zamboni', 'css-min.css')
with open(css_all, 'rb') as fobj:
expected = 'background-image: url(../../img/icons/stars.png'
assert expected in fobj.read()
# Compressed doesn't have any whitespace between `background-image:` and
# the url and the path is slightly different
with open(css_min, 'rb') as fobj:
data = fobj.read()
assert 'background-image:url(' in data
assert 'img/icons/stars.png' in data

Просмотреть файл

@ -3,12 +3,10 @@ import subprocess
import time
from django.conf import settings
from django.contrib.staticfiles.finders import find as find_static_path
import jinja2
from jingo_minify.utils import get_media_url, get_path
try:
from build import BUILD_ID_CSS, BUILD_ID_JS, BUILD_ID_IMG, BUNDLE_HASHES
except ImportError:
@ -29,14 +27,14 @@ def _get_item_path(item):
"""
if is_external(item):
return item
return get_media_url() + item
return settings.STATIC_URL + item
def _get_mtime(item):
"""Get a last-changed timestamp for development."""
if item.startswith(('//', 'http://', 'https://')):
return int(time.time())
return int(os.path.getmtime(get_path(item)))
return int(os.path.getmtime(find_static_path(item)))
def _build_html(items, wrapping):
@ -46,6 +44,17 @@ def _build_html(items, wrapping):
return jinja2.Markup('\n'.join((wrapping % item for item in items)))
def ensure_path_exists(path):
try:
os.makedirs(os.path.dirname(path))
except OSError as e:
# If the directory already exists, that is fine. Otherwise re-raise.
if e.errno != os.errno.EEXIST:
raise
return path
def get_js_urls(bundle, debug=None):
"""
Fetch URLs for the JS files in the requested bundle.
@ -72,19 +81,6 @@ def get_js_urls(bundle, debug=None):
return (_get_item_path('js/%s-min.js?build=%s' % (bundle, build_id,)),)
def _get_compiled_css_url(item):
"""
Compresses a preprocess file and returns its relative compressed URL.
:param item:
Name of the less file to compress into css.
"""
if item.endswith('.less') and getattr(settings, 'LESS_PREPROCESS', False):
compile_css(item)
return item + '.css'
return item
def get_css_urls(bundle, debug=None):
"""
Fetch URLs for the CSS files in the requested bundle.
@ -102,9 +98,11 @@ def get_css_urls(bundle, debug=None):
if debug:
items = []
for item in settings.MINIFY_BUNDLES['css'][bundle]:
if ((item.endswith('.less') and
getattr(settings, 'LESS_PREPROCESS', False)) or
item.endswith(('.sass', '.scss', '.styl'))):
should_compile = (
item.endswith('.less') and
getattr(settings, 'LESS_PREPROCESS', False))
if should_compile:
compile_css(item)
items.append('%s.css' % item)
else:
@ -121,26 +119,18 @@ def get_css_urls(bundle, debug=None):
(bundle, build_id)),)
def ensure_path_exists(path):
try:
os.makedirs(path)
except OSError as e:
# If the directory already exists, that is fine. Otherwise re-raise.
if e.errno != os.errno.EEXIST:
raise
def compile_css(item):
path_src = get_path(item)
path_dst = get_path('%s.css' % item)
path_src = find_static_path(item)
path_dst = os.path.join(
settings.ROOT, 'static', '%s.css' % item)
updated_src = os.path.getmtime(get_path(item))
updated_css = 0 # If the file doesn't exist, force a refresh.
updated_src = os.path.getmtime(find_static_path(item))
updated_dst = 0 # If the file doesn't exist, force a refresh.
if os.path.exists(path_dst):
updated_css = os.path.getmtime(path_dst)
updated_dst = os.path.getmtime(path_dst)
# Is the uncompiled version newer? Then recompile!
if not updated_css or updated_src > updated_css:
if not updated_dst or updated_src > updated_dst:
ensure_path_exists(os.path.dirname(path_dst))
if item.endswith('.less'):
with open(path_dst, 'w') as output:

Просмотреть файл

@ -520,7 +520,6 @@ INSTALLED_APPS = (
'raven.contrib.django',
'rest_framework',
'waffle',
'jingo_minify',
'django_jinja',
'puente',
@ -1694,8 +1693,6 @@ DEV_AGREEMENT_LAST_UPDATED = None
# In production we do not want to allow this.
ALLOW_SELF_REVIEWS = False
# This saves us when we upgrade jingo-minify (jsocol/jingo-minify@916b054c).
JINGO_MINIFY_USE_STATIC = True
# Allow URL style format override. eg. "?format=json"
URL_FORMAT_OVERRIDE = 'format'
@ -1719,11 +1716,11 @@ CDN_HOST = ''
# Static
STATIC_ROOT = path('site-static')
STATIC_URL = '/static/'
JINGO_MINIFY_ROOT = path('static')
STATICFILES_DIRS = (
path('static'),
JINGO_MINIFY_ROOT
)
NETAPP_STORAGE = TMP_PATH
GUARDED_ADDONS_PATH = ROOT + '/guarded-addons'

Просмотреть файл

@ -0,0 +1,261 @@
import os
from django.conf import settings
from django.test.utils import override_settings
import mock
from olympia.amo.utils import from_string
try:
from build import BUILD_ID_CSS, BUILD_ID_JS, BUILD_ID_IMG, BUNDLE_HASHES
except ImportError:
BUILD_ID_CSS = BUILD_ID_JS = BUILD_ID_IMG = 'dev'
BUNDLE_HASHES = {}
TEST_MINIFY_BUNDLES = {
'css': {
'common': ['css/test.css'],
'common_url': ['http://example.com/test.css'],
'common_protocol_less_url': ['//example.com/test.css'],
'common_bundle': ['css/test.css', 'http://example.com/test.css',
'//example.com/test.css',
'https://example.com/test.css'],
'compiled': ['css/plain.css', 'css/less.less']
},
'js': {
'common': ['js/test.js'],
'common_url': ['http://example.com/test.js'],
'common_protocol_less_url': ['//example.com/test.js'],
'common_bundle': ['js/test.js', 'http://example.com/test.js',
'//example.com/test.js',
'https://example.com/test.js'],
},
}
@override_settings(MINIFY_BUNDLES=TEST_MINIFY_BUNDLES)
@mock.patch('olympia.lib.jingo_minify_helpers.time.time')
@mock.patch('olympia.lib.jingo_minify_helpers.os.path.getmtime')
def test_js_helper(getmtime, time):
"""
Given the js() tag if we return the assets that make up that bundle
as defined in settings.MINIFY_BUNDLES.
If we're not in debug mode, we just return a minified url
"""
getmtime.return_value = 1
time.return_value = 1
template = from_string('{{ js("common", debug=True) }}')
rendered = template.render()
expected = '\n'.join([
'<script src="%s?build=1"></script>' % (settings.STATIC_URL + j)
for j in settings.MINIFY_BUNDLES['js']['common']])
assert rendered == expected
template = from_string('{{ js("common", debug=False) }}')
rendered = template.render()
expected = (
'<script src="%sjs/common-min.js?build=%s"></script>' %
(settings.STATIC_URL, BUILD_ID_JS))
assert rendered == expected
template = from_string('{{ js("common_url", debug=True) }}')
rendered = template.render()
expected = '<script src="http://example.com/test.js?build=1"></script>'
assert rendered == expected
template = from_string('{{ js("common_url", debug=False) }}')
rendered = template.render()
expected = (
'<script src="%sjs/common_url-min.js?build=%s"></script>' %
(settings.STATIC_URL, BUILD_ID_JS))
assert rendered == expected
template = from_string('{{ js("common_protocol_less_url", debug=True) }}')
rendered = template.render()
assert rendered == '<script src="//example.com/test.js?build=1"></script>'
template = from_string('{{ js("common_protocol_less_url", debug=False) }}')
rendered = template.render()
expected = (
'<script src="%sjs/common_protocol_less_url-min.js?build=%s"></script>'
% (settings.STATIC_URL, BUILD_ID_JS))
assert rendered == expected
template = from_string('{{ js("common_bundle", debug=True) }}')
rendered = template.render()
assert (
rendered == (
'<script src="%sjs/test.js?build=1"></script>\n'
'<script src="http://example.com/test.js?build=1"></script>\n'
'<script src="//example.com/test.js?build=1"></script>\n'
'<script src="https://example.com/test.js?build=1"></script>'
% settings.STATIC_URL))
template = from_string('{{ js("common_bundle", debug=False) }}')
rendered = template.render()
assert (
rendered ==
'<script src="%sjs/common_bundle-min.js?build=%s"></script>' %
(settings.STATIC_URL, BUILD_ID_JS))
@override_settings(MINIFY_BUNDLES=TEST_MINIFY_BUNDLES)
@mock.patch('olympia.lib.jingo_minify_helpers.time.time')
@mock.patch('olympia.lib.jingo_minify_helpers.os.path.getmtime')
def test_css_helper(getmtime, time):
"""
Given the css() tag if we return the assets that make up that bundle
as defined in settings.MINIFY_BUNDLES.
If we're not in debug mode, we just return a minified url
"""
getmtime.return_value = 1
time.return_value = 1
template = from_string('{{ css("common", debug=True) }}')
rendered = template.render()
expected = "\n".join([
'<link rel="stylesheet" media="all" '
'href="%s?build=1" />' % (settings.STATIC_URL + j)
for j in settings.MINIFY_BUNDLES['css']['common']
])
assert rendered == expected
template = from_string('{{ css("common", debug=False) }}')
rendered = template.render()
expected = (
'<link rel="stylesheet" media="all" '
'href="%scss/common-min.css?build=%s" />'
% (settings.STATIC_URL, BUILD_ID_CSS))
assert rendered == expected
template = from_string('{{ css("common_url", debug=True) }}')
rendered = template.render()
expected = (
'<link rel="stylesheet" media="all" '
'href="http://example.com/test.css?build=1" />')
assert rendered == expected
template = from_string('{{ css("common_url", debug=False) }}')
rendered = template.render()
expected = (
'<link rel="stylesheet" media="all" '
'href="%scss/common_url-min.css?build=%s" />'
% (settings.STATIC_URL, BUILD_ID_CSS))
assert rendered == expected
template = from_string('{{ css("common_protocol_less_url", debug=True) }}')
rendered = template.render()
assert (
rendered == (
'<link rel="stylesheet" media="all" '
'href="//example.com/test.css?build=1" />'))
template = from_string(
'{{ css("common_protocol_less_url", debug=False) }}')
rendered = template.render()
expected = (
'<link rel="stylesheet" media="all" '
'href="%scss/common_protocol_less_url-min.css?build=%s" />'
% (settings.STATIC_URL, BUILD_ID_CSS))
assert rendered == expected
template = from_string('{{ css("common_bundle", debug=True) }}')
rendered = template.render()
assert (
rendered ==
'<link rel="stylesheet" media="all" href="/static/css/test.css?build=1" />\n' # noqa
'<link rel="stylesheet" media="all" href="http://example.com/test.css?build=1" />\n' # noqa
'<link rel="stylesheet" media="all" href="//example.com/test.css?build=1" />\n' # noqa
'<link rel="stylesheet" media="all" href="https://example.com/test.css?build=1" />') # noqa
template = from_string('{{ css("common_bundle", debug=False) }}')
rendered = template.render()
assert (
rendered ==
'<link rel="stylesheet" media="all" '
'href="%scss/common_bundle-min.css?build=%s" />' %
(settings.STATIC_URL, BUILD_ID_CSS))
@override_settings(STATIC_URL='http://example.com/static/',
MEDIA_URL='http://example.com/media/',
MINIFY_BUNDLES=TEST_MINIFY_BUNDLES)
@mock.patch('olympia.lib.jingo_minify_helpers.time.time')
@mock.patch('olympia.lib.jingo_minify_helpers.os.path.getmtime')
def test_css(getmtime, time):
getmtime.return_value = 1
time.return_value = 1
template = from_string('{{ css("common", debug=True) }}')
rendered = template.render()
expected = "\n".join(
['<link rel="stylesheet" media="all" '
'href="%s?build=1" />' % (settings.STATIC_URL + j)
for j in settings.MINIFY_BUNDLES['css']['common']])
assert rendered == expected
@override_settings(MINIFY_BUNDLES={
'css': {'compiled': ['css/impala/buttons.less']}})
@mock.patch('olympia.lib.jingo_minify_helpers.os.path.getmtime')
@mock.patch('olympia.lib.jingo_minify_helpers.subprocess')
@mock.patch('__builtin__.open', spec=True)
def test_compiled_css(open_mock, subprocess_mock, getmtime_mock):
getmtime_mock.side_effect = [
# The first call is for the source
1531144805.1225898,
# The second call is for the destination
1530885814.6340182]
from_string('{{ css("compiled", debug=True) }}")').render()
source = os.path.realpath(os.path.join(
settings.ROOT, 'static/css/impala/buttons.less'))
assert subprocess_mock.Popen.mock_calls == [
mock.call([settings.LESS_BIN, source], stdout=mock.ANY)]
@override_settings(STATIC_URL='http://example.com/static/',
MEDIA_URL='http://example.com/media/')
@mock.patch('olympia.lib.jingo_minify_helpers.time.time')
@mock.patch('olympia.lib.jingo_minify_helpers.os.path.getmtime')
def test_js(getmtime, time):
getmtime.return_value = 1
time.return_value = 1
template = from_string('{{ js("common", debug=True) }}')
rendered = template.render()
expected = "\n".join(
['<script src="%s?build=1"></script>' % (settings.STATIC_URL + j)
for j in settings.MINIFY_BUNDLES['js']['common']])
assert rendered == expected

15
tox.ini
Просмотреть файл

@ -18,39 +18,39 @@ whitelist_externals =
[testenv:es]
commands =
make -f Makefile-docker install_python_test_dependencies
pytest -m "es_tests and not needs_locales_compilation" --ignore=tests/ui/ -v {posargs}
pytest -m "es_tests and not needs_locales_compilation and not static_assets" --ignore=tests/ui/ -v src/olympia/{posargs}
[testenv:addons]
commands =
make -f Makefile-docker install_python_test_dependencies
pytest -n 2 -m 'not es_tests and not needs_locales_compilation' -v src/olympia/addons/ {posargs}
pytest -n 2 -m 'not es_tests and not needs_locales_compilation and not static_assets' -v src/olympia/addons/ {posargs}
[testenv:devhub]
commands =
make -f Makefile-docker install_python_test_dependencies install_node_dependencies
pytest -n 2 -m 'not es_tests and not needs_locales_compilation' -v src/olympia/devhub/ {posargs}
pytest -n 2 -m 'not es_tests and not needs_locales_compilation and not static_assets' -v src/olympia/devhub/ {posargs}
[testenv:reviewers]
commands =
make -f Makefile-docker install_python_test_dependencies
pytest -n 2 -m 'not es_tests and not needs_locales_compilation' -v src/olympia/reviewers/ {posargs}
pytest -n 2 -m 'not es_tests and not needs_locales_compilation and not static_assets' -v src/olympia/reviewers/ {posargs}
[testenv:amo-locales-and-signing]
commands =
make -f Makefile-docker install_python_test_dependencies install_node_dependencies
pytest -n 2 -m 'not es_tests and not needs_locales_compilation' -v src/olympia/amo/ src/olympia/lib/crypto/ src/olympia/signing {posargs}
pytest -n 2 -m 'not es_tests and not needs_locales_compilation and not static_assets' -v src/olympia/amo/ src/olympia/lib/crypto/ src/olympia/signing {posargs}
bash {toxinidir}/locale/compile-mo.sh {toxinidir}/locale/
pytest -n 2 -m 'needs_locales_compilation' -v src/olympia/ {posargs}
[testenv:users-and-accounts]
commands =
make -f Makefile-docker install_python_test_dependencies
pytest -n 2 -m 'not es_tests and not needs_locales_compilation' -v src/olympia/users/ src/olympia/accounts/ {posargs}
pytest -n 2 -m 'not es_tests and not needs_locales_compilation and not static_assets' -v src/olympia/users/ src/olympia/accounts/ {posargs}
[testenv:main]
commands =
make -f Makefile-docker install_python_test_dependencies install_node_dependencies
pytest -n 2 -m 'not es_tests and not needs_locales_compilation' -v src/olympia/ --ignore src/olympia/addons/ --ignore src/olympia/devhub/ --ignore src/olympia/reviewers/ --ignore src/olympia/amo/ --ignore src/olympia/users/ --ignore src/olympia/accounts/ --ignore src/olympia/lib/crypto --ignore src/olympia/signing {posargs}
pytest -n 2 -m 'not es_tests and not needs_locales_compilation and not static_assets' -v src/olympia/ --ignore src/olympia/addons/ --ignore src/olympia/devhub/ --ignore src/olympia/reviewers/ --ignore src/olympia/amo/ --ignore src/olympia/users/ --ignore src/olympia/accounts/ --ignore src/olympia/lib/crypto --ignore src/olympia/signing {posargs}
[testenv:ui-tests]
commands =
@ -61,6 +61,7 @@ commands =
[testenv:assets]
commands =
make -f Makefile-docker update_deps
pytest -m "static_assets" --ignore=tests/ui/ -v src/olympia/ {posargs}
make -f Makefile-docker update_assets
[testenv:codestyle]