diff --git a/lms/djangoapps/courseware/tests/load_tests/README.md b/lms/djangoapps/courseware/tests/load_tests/README.md new file mode 100644 index 0000000000..09d8797947 --- /dev/null +++ b/lms/djangoapps/courseware/tests/load_tests/README.md @@ -0,0 +1,4 @@ +# Load Testing + +Scripts for load testing the courseware app, +mostly using [multimechanize](http://testutils.org/multi-mechanize/) diff --git a/lms/djangoapps/courseware/tests/load_tests/custom_response/README.md b/lms/djangoapps/courseware/tests/load_tests/custom_response/README.md new file mode 100644 index 0000000000..2120b06b7e --- /dev/null +++ b/lms/djangoapps/courseware/tests/load_tests/custom_response/README.md @@ -0,0 +1,47 @@ +# Custom Response Load Test + +## Optional Installations + +* [memcached](http://pypi.python.org/pypi/python-memcached/): Install this +and make sure it is running, or the Capa problem will not cache results. + +* [AppArmor](http://wiki.apparmor.net): Follow the instructions in +`common/lib/codejail/README` to set up the Python sandbox environment. +If you do not set up the sandbox, the tests will still execute code in the CustomResponse, +so you can still run the tests. + +* [matplotlib](http://matplotlib.org): Multi-mechanize uses this to create graphs. + + +## Running the Tests + +This test simulates student submissions for a custom response problem. + +You can run the test using: + + multimech-run custom_response + +You can configure the parameters in `customresponse/config.cfg`, +and you can change the CustomResponse script and student submissions +in `customresponse/test_scripts/v_user.py`. + +## Components Under Test + +Components under test: + +* Python sandbox (see `common/lib/codejail`), which uses `AppArmor` +* Caching (see `common/lib/capa/capa/safe_exec/`), which uses `memcache` in production + +Components NOT under test: + +* Django views +* `XModule` +* gunicorn + +This allows us to avoid creating courses in mongo, logging in, using CSRF tokens, +and other inconveniences. Instead, we create a capa problem (from the capa package), +pass it Django's memcache backend, and pass the problem student submissions. + +Even though the test uses `capa.capa_problem.LoncapaProblem` directly, +the `capa` should not depend on Django. For this reason, we put the +test in the `courseware` Django app. diff --git a/lms/djangoapps/courseware/tests/load_tests/custom_response/config.cfg b/lms/djangoapps/courseware/tests/load_tests/custom_response/config.cfg new file mode 100644 index 0000000000..c75f02a669 --- /dev/null +++ b/lms/djangoapps/courseware/tests/load_tests/custom_response/config.cfg @@ -0,0 +1,22 @@ + +[global] +run_time = 240 +rampup = 30 +results_ts_interval = 10 +progress_bar = on +console_logging = off +xml_report = off + + +[user_group-1] +threads = 10 +script = v_user.py + +[user_group-2] +threads = 10 +script = v_user.py + +[user_group-3] +threads = 10 +script = v_user.py + diff --git a/lms/djangoapps/courseware/tests/load_tests/custom_response/test_scripts/v_user.py b/lms/djangoapps/courseware/tests/load_tests/custom_response/test_scripts/v_user.py new file mode 100644 index 0000000000..9bfc39e55b --- /dev/null +++ b/lms/djangoapps/courseware/tests/load_tests/custom_response/test_scripts/v_user.py @@ -0,0 +1,115 @@ +""" User script for load testing CustomResponse """ + +from capa.tests.response_xml_factory import CustomResponseXMLFactory +import capa.capa_problem as lcp +from xmodule.x_module import ModuleSystem +import mock +import fs.osfs +import random +import textwrap + +# Use memcache running locally +CACHE_SETTINGS = { + 'default': { + 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', + 'LOCATION': '127.0.0.1:11211' + }, +} + +# Configure settings so Django will let us import its cache wrapper +# Caching is the only part of Django being tested +from django.conf import settings +settings.configure(CACHES=CACHE_SETTINGS) + +from django.core.cache import cache + +# Script to install as the checker for the CustomResponse +TEST_SCRIPT = textwrap.dedent(""" + def check_func(expect, answer_given): + return {'ok': answer_given == expect, 'msg': 'Message text'} +""") + +# Submissions submitted by the student +TEST_SUBMISSIONS = [random.randint(-100, 100) for i in range(100)] + +class TestContext(object): + """ One-time set up for the test that is shared across transactions. + Uses a Singleton design pattern.""" + + SINGLETON = None + NUM_UNIQUE_SEEDS = 20 + + @classmethod + def singleton(cls): + """ Return the singleton, creating one if it does not already exist.""" + + # If we haven't created the singleton yet, create it now + if cls.SINGLETON is None: + + # Create a mock ModuleSystem, installing our cache + system = mock.MagicMock(ModuleSystem) + system.render_template = lambda template, context: "