Files
edx-platform/pavelib/paver_tests/test_assets.py
2021-03-05 15:24:59 +05:00

387 lines
15 KiB
Python

"""Unit tests for the Paver asset tasks."""
import os
from unittest import TestCase
from unittest.mock import patch
import ddt
import paver.tasks
from paver.easy import call_task, path
from watchdog.observers import Observer
from pavelib.assets import COLLECTSTATIC_LOG_DIR_ARG, collect_assets
from ..utils.envs import Env
from .utils import PaverTestCase
ROOT_PATH = path(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
TEST_THEME_DIR = ROOT_PATH / "common/test/test-theme"
@ddt.ddt
class TestPaverAssetTasks(PaverTestCase):
"""
Test the Paver asset tasks.
"""
@ddt.data(
[""],
["--force"],
["--debug"],
["--system=lms"],
["--system=lms --force"],
["--system=studio"],
["--system=studio --force"],
["--system=lms,studio"],
["--system=lms,studio --force"],
)
@ddt.unpack
def test_compile_sass(self, options):
"""
Test the "compile_sass" task.
"""
parameters = options.split(" ")
system = []
if '--system=studio' not in parameters:
system += ['lms']
if '--system=lms' not in parameters:
system += ['studio']
debug = '--debug' in parameters
force = '--force' in parameters
self.reset_task_messages()
call_task('pavelib.assets.compile_sass', options={'system': system, 'debug': debug, 'force': force})
expected_messages = []
if force:
expected_messages.append('rm -rf common/static/css/*.css')
expected_messages.append('libsass common/static/sass')
if "lms" in system:
if force:
expected_messages.append('rm -rf lms/static/css/*.css')
expected_messages.append('libsass lms/static/sass')
expected_messages.append(
'rtlcss lms/static/css/bootstrap/lms-main.css lms/static/css/bootstrap/lms-main-rtl.css'
)
expected_messages.append(
'rtlcss lms/static/css/discussion/lms-discussion-bootstrap.css'
' lms/static/css/discussion/lms-discussion-bootstrap-rtl.css'
)
if force:
expected_messages.append('rm -rf lms/static/certificates/css/*.css')
expected_messages.append('libsass lms/static/certificates/sass')
if "studio" in system:
if force:
expected_messages.append('rm -rf cms/static/css/*.css')
expected_messages.append('libsass cms/static/sass')
expected_messages.append(
'rtlcss cms/static/css/bootstrap/studio-main.css cms/static/css/bootstrap/studio-main-rtl.css'
)
assert len(self.task_messages) == len(expected_messages)
@ddt.ddt
class TestPaverThemeAssetTasks(PaverTestCase):
"""
Test the Paver asset tasks.
"""
@ddt.data(
[""],
["--force"],
["--debug"],
["--system=lms"],
["--system=lms --force"],
["--system=studio"],
["--system=studio --force"],
["--system=lms,studio"],
["--system=lms,studio --force"],
)
@ddt.unpack
def test_compile_theme_sass(self, options):
"""
Test the "compile_sass" task.
"""
parameters = options.split(" ")
system = []
if '--system=studio' not in parameters:
system += ['lms']
if "--system=lms" not in parameters:
system += ['studio']
debug = '--debug' in parameters
force = '--force' in parameters
self.reset_task_messages()
call_task(
'pavelib.assets.compile_sass',
options=dict(
system=system,
debug=debug,
force=force,
theme_dirs=[TEST_THEME_DIR.dirname()],
themes=[TEST_THEME_DIR.basename()]
),
)
expected_messages = []
if force:
expected_messages.append('rm -rf common/static/css/*.css')
expected_messages.append('libsass common/static/sass')
if 'lms' in system:
expected_messages.append('mkdir_p ' + repr(TEST_THEME_DIR / 'lms/static/css'))
if force:
expected_messages.append(
'rm -rf {test_theme_dir}/lms/static/css/*.css'.format(test_theme_dir=str(TEST_THEME_DIR))
)
expected_messages.append("libsass lms/static/sass")
expected_messages.append(
'rtlcss {test_theme_dir}/lms/static/css/bootstrap/lms-main.css'
' {test_theme_dir}/lms/static/css/bootstrap/lms-main-rtl.css'.format(
test_theme_dir=str(TEST_THEME_DIR),
)
)
expected_messages.append(
'rtlcss {test_theme_dir}/lms/static/css/discussion/lms-discussion-bootstrap.css'
' {test_theme_dir}/lms/static/css/discussion/lms-discussion-bootstrap-rtl.css'.format(
test_theme_dir=str(TEST_THEME_DIR),
)
)
if force:
expected_messages.append(
'rm -rf {test_theme_dir}/lms/static/css/*.css'.format(test_theme_dir=str(TEST_THEME_DIR))
)
expected_messages.append(
'libsass {test_theme_dir}/lms/static/sass'.format(test_theme_dir=str(TEST_THEME_DIR))
)
if force:
expected_messages.append('rm -rf lms/static/css/*.css')
expected_messages.append('libsass lms/static/sass')
expected_messages.append(
'rtlcss lms/static/css/bootstrap/lms-main.css lms/static/css/bootstrap/lms-main-rtl.css'
)
expected_messages.append(
'rtlcss lms/static/css/discussion/lms-discussion-bootstrap.css'
' lms/static/css/discussion/lms-discussion-bootstrap-rtl.css'
)
if force:
expected_messages.append('rm -rf lms/static/certificates/css/*.css')
expected_messages.append('libsass lms/static/certificates/sass')
if "studio" in system:
expected_messages.append('mkdir_p ' + repr(TEST_THEME_DIR / 'cms/static/css'))
if force:
expected_messages.append(
'rm -rf {test_theme_dir}/cms/static/css/*.css'.format(test_theme_dir=str(TEST_THEME_DIR))
)
expected_messages.append('libsass cms/static/sass')
expected_messages.append(
'rtlcss {test_theme_dir}/cms/static/css/bootstrap/studio-main.css'
' {test_theme_dir}/cms/static/css/bootstrap/studio-main-rtl.css'.format(
test_theme_dir=str(TEST_THEME_DIR),
)
)
if force:
expected_messages.append(
'rm -rf {test_theme_dir}/cms/static/css/*.css'.format(test_theme_dir=str(TEST_THEME_DIR))
)
expected_messages.append(
'libsass {test_theme_dir}/cms/static/sass'.format(test_theme_dir=str(TEST_THEME_DIR))
)
if force:
expected_messages.append('rm -rf cms/static/css/*.css')
expected_messages.append('libsass cms/static/sass')
expected_messages.append(
'rtlcss cms/static/css/bootstrap/studio-main.css cms/static/css/bootstrap/studio-main-rtl.css'
)
assert len(self.task_messages) == len(expected_messages)
class TestPaverWatchAssetTasks(TestCase):
"""
Test the Paver watch asset tasks.
"""
def setUp(self):
self.expected_sass_directories = [
path('common/static/sass'),
path('common/static'),
path('node_modules/@edx'),
path('node_modules'),
path('lms/static/sass/partials'),
path('lms/static/sass'),
path('lms/static/certificates/sass'),
path('cms/static/sass'),
path('cms/static/sass/partials'),
]
# Reset the options that paver stores in a global variable (thus polluting tests)
if 'pavelib.assets.watch_assets' in paver.tasks.environment.options:
del paver.tasks.environment.options['pavelib.assets.watch_assets']
super().setUp()
def tearDown(self):
self.expected_sass_directories = []
super().tearDown()
def test_watch_assets(self):
"""
Test the "compile_sass" task.
"""
with patch('pavelib.assets.SassWatcher.register') as mock_register:
with patch('pavelib.assets.Observer.start'):
with patch('pavelib.assets.execute_webpack_watch') as mock_webpack:
call_task(
'pavelib.assets.watch_assets',
options={"background": True},
)
assert mock_register.call_count == 2
assert mock_webpack.call_count == 1
sass_watcher_args = mock_register.call_args_list[0][0]
assert isinstance(sass_watcher_args[0], Observer)
assert isinstance(sass_watcher_args[1], list)
assert len(sass_watcher_args[1]) == len(self.expected_sass_directories)
def test_watch_theme_assets(self):
"""
Test the Paver watch asset tasks with theming enabled.
"""
self.expected_sass_directories.extend([
path(TEST_THEME_DIR) / 'lms/static/sass',
path(TEST_THEME_DIR) / 'lms/static/sass/partials',
path(TEST_THEME_DIR) / 'lms/static/certificates/sass',
path(TEST_THEME_DIR) / 'cms/static/sass',
path(TEST_THEME_DIR) / 'cms/static/sass/partials',
])
with patch('pavelib.assets.SassWatcher.register') as mock_register:
with patch('pavelib.assets.Observer.start'):
with patch('pavelib.assets.execute_webpack_watch') as mock_webpack:
call_task(
'pavelib.assets.watch_assets',
options={
"background": True,
"theme_dirs": [TEST_THEME_DIR.dirname()],
"themes": [TEST_THEME_DIR.basename()]
},
)
assert mock_register.call_count == 2
assert mock_webpack.call_count == 1
sass_watcher_args = mock_register.call_args_list[0][0]
assert isinstance(sass_watcher_args[0], Observer)
assert isinstance(sass_watcher_args[1], list)
assert len(sass_watcher_args[1]) == len(self.expected_sass_directories)
@ddt.ddt
class TestCollectAssets(PaverTestCase):
"""
Test the collectstatic process call.
ddt data is organized thusly:
* debug: whether or not collect_assets is called with the debug flag
* specified_log_location: used when collect_assets is called with a specific
log location for collectstatic output
* expected_log_location: the expected string to be used for piping collectstatic logs
"""
@ddt.data(
[{
"collect_log_args": {}, # Test for default behavior
"expected_log_location": "> /dev/null"
}],
[{
"collect_log_args": {COLLECTSTATIC_LOG_DIR_ARG: "/foo/bar"},
"expected_log_location": "> /foo/bar/lms-collectstatic.log"
}], # can use specified log location
[{
"systems": ["lms", "cms"],
"collect_log_args": {},
"expected_log_location": "> /dev/null"
}], # multiple systems can be called
)
@ddt.unpack
def test_collect_assets(self, options):
"""
Ensure commands sent to the environment for collect_assets are as expected
"""
specified_log_loc = options.get("collect_log_args", {})
specified_log_dict = specified_log_loc
log_loc = options.get("expected_log_location", "> /dev/null")
systems = options.get("systems", ["lms"])
if specified_log_loc is None:
collect_assets(
systems,
Env.DEVSTACK_SETTINGS
)
else:
collect_assets(
systems,
Env.DEVSTACK_SETTINGS,
**specified_log_dict
)
self._assert_correct_messages(log_location=log_loc, systems=systems)
def test_collect_assets_debug(self):
"""
When the method is called specifically with None for the collectstatic log dir, then
it should run in debug mode and pipe to console.
"""
expected_log_loc = ""
systems = ["lms"]
kwargs = {COLLECTSTATIC_LOG_DIR_ARG: None}
collect_assets(systems, Env.DEVSTACK_SETTINGS, **kwargs)
self._assert_correct_messages(log_location=expected_log_loc, systems=systems)
def _assert_correct_messages(self, log_location, systems):
"""
Asserts that the expected commands were run.
We just extract the pieces we care about here instead of specifying an
exact command, so that small arg changes don't break this test.
"""
for i, sys in enumerate(systems):
msg = self.task_messages[i]
assert msg.startswith(f'python manage.py {sys}')
assert ' collectstatic ' in msg
assert f'--settings={Env.DEVSTACK_SETTINGS}' in msg
assert msg.endswith(f' {log_location}')
@ddt.ddt
class TestUpdateAssetsTask(PaverTestCase):
"""
These are nearly end-to-end tests, because they observe output from the commandline request,
but do not actually execute the commandline on the terminal/process
"""
@ddt.data(
[{"expected_substring": "> /dev/null"}], # go to /dev/null by default
[{"cmd_args": ["--debug"], "expected_substring": "collectstatic"}] # TODO: make this regex
)
@ddt.unpack
def test_update_assets_task_collectstatic_log_arg(self, options):
"""
Scoped test that only looks at what is passed to the collecstatic options
"""
cmd_args = options.get("cmd_args", [""])
expected_substring = options.get("expected_substring", None)
call_task('pavelib.assets.update_assets', args=cmd_args)
self.assertTrue(
self._is_substring_in_list(self.task_messages, expected_substring),
msg=f"{expected_substring} not found in messages"
)
def _is_substring_in_list(self, messages_list, expected_substring):
"""
Return true a given string is somewhere in a list of strings
"""
for message in messages_list:
if expected_substring in message:
return True
return False