def test_load_settings_missing_required_secrets(mock_resource_filename): """ missing required setting in secrets document should raise an exception """ mock_resource_filename.return_value = 'tests/foo.yml' with pytest.raises(settings.MissingRequiredSettingError): settings.init('loadtests.foo.locustfile', required_secrets=['MISSING_REQUIRED_KEY'])
def test_load_settings_empty_secrets(mock_resource_filename): """ load a valid settings file with an empty "secrets" yaml document. """ mock_resource_filename.return_value = 'tests/foo_empty_secrets_doc.yml' settings.init('loadtests.foo.locustfile', ['REQUIRED_KEY']) with open('tests/foo_empty_secrets_doc.yml') as foo: foo_data, foo_secrets = yaml.load_all(foo) assert settings.data == foo_data assert settings.secrets == {} assert foo_secrets is None
def test_load_settings_no_secrets(mock_resource_filename): """ load a valid settings file without a "secrets" yaml document. """ mock_resource_filename.return_value = 'tests/foo_no_secrets_doc.yml' settings.init('loadtests.foo.locustfile', ['REQUIRED_KEY']) with open('tests/foo_no_secrets_doc.yml') as foo: settings_documents = yaml.load_all(foo) foo_data = settings_documents.next() with pytest.raises(StopIteration): settings_documents.next() assert settings.data == foo_data assert settings.secrets == {}
def test_load_settings(mock_resource_filename): """ normal successful circumstances: load a valid settings file with a mix of optional/required/public/secret settings. """ mock_resource_filename.return_value = 'tests/foo.yml' settings.init('loadtests.foo.locustfile', ['REQUIRED_KEY'], ['REQUIRED_SECRET_KEY']) mock_resource_filename.assert_called_once_with('settings_files', 'foo.yml') with open('tests/foo.yml') as foo: foo_data, foo_secrets = yaml.load_all(foo) assert settings.data == foo_data assert settings.secrets == foo_secrets
# due to locust sys.path manipulation, we need to re-add the project root. sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) import random import time from locust import HttpLocust, TaskSet, task, events from helpers import settings, markers settings.init( __name__, required_data=[ 'CMS_USER_EMAIL', 'TEST_FILE', 'NUM_PARALLEL_COURSES', ], required_secrets=[ 'CMS_USER_PASSWORD', ], ) markers.install_event_markers() class CourseImport(TaskSet): "Course import task set -- creates course and imports tarballs." def on_start(self): "Setup method; log in to studio and create courses."
import urllib3 import sys import os # due to locust sys.path manipulation, we need to re-add the project root. sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) sys.path.append(os.path.dirname(os.path.dirname(__file__))) from helpers import settings, markers from locust import HttpLocust, TaskSet settings.init( 'ecommerce_deadlock.', # This is a hack to allow the settings code to find the settings file. required_data=['ECOMMERCE_HOST', 'LMS_HOST'], ) from loadtests.ecommerce_deadlock.tasks.LMS_user_tasks import ManualUserBasketTaskSet, AutoAuthUserBasketTaskSet from loadtests.ecommerce_deadlock.tasks.ecommerce_user_tasks import EcommerceWorkerBasketTaskSet urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) markers.install_event_markers() class EcommerceTaskSet(TaskSet): tasks = { ManualUserBasketTaskSet: 3, # AutoAuthUserBasketTaskSet: 1, EcommerceWorkerBasketTaskSet: 1 } class EcommerceLocust(HttpLocust): # Explicitly set host to avoid confusion since we are testing with multiple IDAs
from locust import HttpLocust from authentication_views import AuthenticationViewsTasks from courseware_views import CoursewareViewsTasks from forums import ForumsTasks, SeedForumsTasks from base import LmsTasks from proctoring import ProctoredExamTasks from module_render import ModuleRenderTasks from wiki_views import WikiViewTask from tracking import TrackingTasks from helpers import settings, markers settings.init(__name__, required_data=[ 'courses', 'LOCUST_TASK_SET', 'LOCUST_MIN_WAIT', 'LOCUST_MAX_WAIT', ]) markers.install_event_markers() class LmsTest(LmsTasks): """ TaskSet that pulls together all the LMS-related TaskSets into a single unified test. See util/lms_tx_distribution.sh for instructions on generating the data below. Traffic Distribution on courses.edx.org (last 7d as of 2016-11-16):
import sys # due to locust sys.path manipulation, we need to re-add the project root. sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) import random from locust import HttpLocust, task, TaskSet from locust.clients import HttpSession from helpers import settings from helpers.api import LocustEdxRestApiClient settings.init( __name__, required_data=['programs'], required_secrets=['oauth'], ) class SelfInterruptingTaskSet(TaskSet): @task(1) def stop(self): self.interrupt() class CatalogTaskSet(SelfInterruptingTaskSet): catalog_id = 1 @task(20) def get_catalog_courses(self):
# due to locust sys.path manipulation, we need to re-add the project root. sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) from collections import deque import json import random import string from locust import HttpLocust, task from helpers.auto_auth_tasks import AutoAuthTasks from helpers import settings settings.init(__name__, required=[ 'COURSE_ID', 'LOCUST_MIN_WAIT', 'LOCUST_MAX_WAIT', ]) _dummy_chars = string.lowercase + ' ' def _dummy_text(minlen, maxlen): """ Generate dummy text for forum submissions. """ return "".join(random.choice(_dummy_chars) for _ in xrange(minlen, random.randrange(minlen + 1, maxlen))) class TeamsDiscussionTasks(AutoAuthTasks):
# due to locust sys.path manipulation, we need to re-add the project root. sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) from locust import HttpLocust from courseware_views import CoursewareViewsTasks from forums import ForumsTasks, SeedForumsTasks from base import LmsTasks from proctoring import ProctoredExamTasks from module_render import ModuleRenderTasks from wiki_views import WikiViewTask from tracking import TrackingTasks from helpers import settings settings.init(__name__, required=["COURSE_ID", "COURSE_DATA", "LOCUST_TASK_SET", "LOCUST_MIN_WAIT", "LOCUST_MAX_WAIT"]) class LmsTest(LmsTasks): """ TaskSet that pulls together all the LMS-related TaskSets into a single unified test. See util/lms_tx_distribution.sh for instructions on generating the data below. Traffic Distribution on courses.edx.org (last 7d as of 2016-11-16): /openedx.core.djangoapps.heartbeat.views Total, 26.34% /track.views Total, 24.24% (TrackingTasks) /courseware.module_render Total, 21.74% (ModuleRenderTasks) /enrollment.views Total, 3.60%
import sys # due to locust sys.path manipulation, we need to re-add the project root. sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) from collections import deque import random from locust import task, HttpLocust from locust.clients import HttpSession from helpers import settings, markers from helpers.api import LocustEdxRestApiClient from helpers.auto_auth_tasks import AutoAuthTasks settings.init(__name__, required_data=['credentials'], required_secrets=['oauth']) markers.install_event_markers() CREDENTIAL_SERVICE_URL = settings.data['credentials']['url']['service'] LMS_ROOT_URL = settings.data['credentials']['lms_url_root'] PROGRAM_UUID = settings.data['credentials']['program_uuid'] USERNAME = settings.data['credentials']['username'] class CredentialTaskSet(AutoAuthTasks): """Tasks exercising Credential functionality.""" # Keep track of credential UUIDs created during a test, so they can be used to formulate read requests, and patch # requests. A deque is used instead of a list in order to enforce maximum length. _user_credentials = deque(maxlen=1000)
import os import sys # due to locust sys.path manipulation, we need to re-add the project root. sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) import random from locust import HttpLocust, task, TaskSet from locust.clients import HttpSession from helpers import settings from helpers.api import LocustEdxRestApiClient settings.init(__name__, required=['oauth', 'programs']) class SelfInterruptingTaskSet(TaskSet): @task(1) def stop(self): self.interrupt() class CatalogTaskSet(SelfInterruptingTaskSet): catalog_id = 1 @task(20) def get_catalog_courses(self): """Retrieve all courses associated with a catalog.""" self.client.catalogs(self.catalog_id).courses.get()
import os import sys # due to locust sys.path manipulation, we need to re-add the project root. sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) from locust import HttpLocust, TaskSet from baskets import BasketsTasks from payment import CybersourcePaymentTasks from helpers import settings settings.init(__name__, required=['ecommerce', 'jwt']) class EcommerceTest(TaskSet): """Load test exercising ecommerce-related operations on the LMS and Otto. Execution probabilities are derived from a conservative estimate from marketing placing the percentage of paid enrollments at 2% of all enrollments. """ tasks = { BasketsTasks: 50, CybersourcePaymentTasks: 1, } class EcommerceUser(HttpLocust): """Representation of an HTTP "user".
# due to locust sys.path manipulation, we need to re-add the project root. sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) import requests.exceptions import json import random import string from locust import HttpLocust, task from helpers.auto_auth_tasks import AutoAuthTasks from helpers import settings, markers settings.init(__name__, required_data=[ 'html', 'video', 'COURSE_ID', 'LOCUST_MIN_WAIT', 'LOCUST_MAX_WAIT', ]) markers.install_event_markers() _dummy_chars = string.lowercase + ' ' class CompletionAPITaskSet(AutoAuthTasks): """ Tests the course completion API. """ def __init__(self, *args, **kwargs):
import random from locust import HttpLocust, task, TaskSet from locust.clients import HttpSession from helpers import settings from helpers.api import LocustEdxRestApiClient settings.init(__name__, required=['oauth']) class SelfInterruptingTaskSet(TaskSet): @task(1) def stop(self): self.interrupt() class CatalogTaskSet(SelfInterruptingTaskSet): catalog_id = 1 @task(20) def get_catalog_courses(self): """Retrieve all courses associated with a catalog.""" self.client.catalogs(self.catalog_id).courses.get() @task(10) def list_courses_with_query(self): """Query the courses.""" self.client.courses.get(q='organizations:(MITx OR HarvardX)')
from contextlib import contextmanager from copy import copy import json from locust import HttpLocust, task, TaskSet import logging import random from helpers import settings # NOTE: the host URL passed in via command-line '--host' flag is the host of # the LMS! Make sure to set the notes service URL via the NOTES_HOST setting. settings.init(__name__, required_data=[ 'courses', 'NOTES_HOST', 'NUM_NOTES', 'NUM_WORDS', 'NUM_TAGS', 'NUM_SEARCH_TERMS', 'LOCUST_TASK_SET', 'LOCUST_MIN_WAIT', 'LOCUST_MAX_WAIT', ]) from helpers.mixins import EnrollmentTaskSetMixin from helpers.edx_app import EdxAppTasks # Constants used by the LMS when searching student notes. HIGHLIGHT_TAG = 'span' HIGHLIGHT_CLASS = 'note-highlight' # Internal constants DATA_DIRECTORY = os.path.join(os.path.dirname(__file__), 'notes_data/')
Performance tests for enrollment API. """ import os import sys # due to locust sys.path manipulation, we need to re-add the project root. sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) import json import uuid import random from locust import HttpLocust, TaskSet, task from helpers import settings settings.init(__name__, required=[ 'COURSE_ID_LIST', ]) EMAIL_USERNAME = "******" EMAIL_URL = "simulator.amazonses.com" # Set a password for the users USER_PASSWORD = "******" ENROLLMENT_API_BASE_URL = "/api/enrollment/v1" class NotAuthorizedException(Exception): """The API returned an HTTP status 403 """ pass class EnrollmentApi(object):
""" import os import sys # due to locust sys.path manipulation, we need to re-add the project root. sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) import random import time from locust import HttpLocust, TaskSet, task, events from helpers import settings settings.init(__name__, required=[ 'CMS_USER_EMAIL', 'CMS_USER_PASSWORD', 'TEST_FILE', 'NUM_PARALLEL_COURSES', ]) class CourseImport(TaskSet): "Course import task set -- creates course and imports tarballs." def on_start(self): "Setup method; log in to studio and create courses." self.login() for i in xrange(settings.data['NUM_PARALLEL_COURSES']): self.create_course(i)
from locust.exception import LocustError from warnings import filterwarnings import MySQLdb as Database from helpers.raw_logs import RawLogger from helpers import datadog_reporting # load the test settings BEFORE django settings where they are used for # database configuration from helpers import settings settings.init(__name__, required=[ 'DB_ENGINE', 'DB_HOST', 'DB_NAME', 'DB_PORT', 'DB_USER', 'DB_PASSWORD', ]) os.environ["DJANGO_SETTINGS_MODULE"] = "csm.locustsettings" # Load django settings here to trigger edx-platform sys.path manipulations from django.conf import settings as django_settings # noqa django_settings.INSTALLED_APPS import courseware.user_state_client as user_state_client # noqa from student.tests.factories import UserFactory # noqa from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator # noqa LOG = logging.getLogger(__file__)
import os import sys # due to locust sys.path manipulation, we need to re-add the project root. sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) from locust import HttpLocust, TaskSet from baskets import BasketsTasks from payment import CybersourcePaymentTasks from helpers import settings settings.init( __name__, required_data=['ecommerce'], required_secrets=['ecommerce', 'jwt'], ) class EcommerceTest(TaskSet): """Load test exercising ecommerce-related operations on the LMS and Otto. Execution probabilities are derived from a conservative estimate from marketing placing the percentage of paid enrollments at 2% of all enrollments. """ tasks = { BasketsTasks: 50, CybersourcePaymentTasks: 1, }
Load tests for course import from studio. By default, this tests loading a relatively small course. I recommend exporting a large course from edX and using it here. """ import random import time from locust import HttpLocust, TaskSet, task, events from helpers import settings settings.init(__name__, required=[ 'CMS_USER_EMAIL', 'CMS_USER_PASSWORD', 'TEST_FILE', 'NUM_PARALLEL_COURSES', ]) class CourseImport(TaskSet): "Course import task set -- creates course and imports tarballs." def on_start(self): "Setup method; log in to studio and create courses." self.login() for i in xrange(settings.data['NUM_PARALLEL_COURSES']): self.create_course(i)
GetCommentsTask, GetThreadListTask, GetThreadWithCommentsTask, PatchCommentsTask, PatchThreadsTask, PostCommentsTask, PostThreadsTask, ) requests.packages.urllib3.disable_warnings() from helpers import settings settings.init(__name__, required=[ 'COURSE_ID', 'VERBOSE', 'LOCUST_TASK_SET', 'LOCUST_MIN_WAIT', 'LOCUST_MAX_WAIT', ]) class DiscussionsApiTest(DiscussionsApiTasks): """ This is a repeatable baseline test which utilizes the most used requests. PATCH, POST, and DELETE can affect the response times of the discussions API over time. Since the GET Thread and GET Thread List are used significantly more, we use GET Thread and GET Thread List as the baseline since the requests are read only and the test is repeatable. """
from locust import Locust, TaskSet, task, events, web from locust.exception import LocustError from warnings import filterwarnings import MySQLdb as Database from helpers.raw_logs import RawLogger from helpers import datadog_reporting # load the test settings BEFORE django settings where they are used for # database configuration from helpers import settings settings.init(__name__, required=[ 'DB_ENGINE', 'DB_HOST', 'DB_NAME', 'DB_PORT', 'DB_USER', 'DB_PASSWORD', ]) os.environ["DJANGO_SETTINGS_MODULE"] = "csm.locustsettings" # Load django settings here to trigger edx-platform sys.path manipulations from django.conf import settings as django_settings # noqa django_settings.INSTALLED_APPS import courseware.user_state_client as user_state_client # noqa from student.tests.factories import UserFactory # noqa from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator # noqa LOG = logging.getLogger(__file__) RANDOM_CHARACTERS = [random.choice(string.ascii_letters + string.digits) for __ in xrange(1000)]