diff --git a/common/djangoapps/util/tests/test_codejail_includes.py b/common/djangoapps/util/tests/test_codejail_includes.py new file mode 100644 index 0000000000..0e56240a54 --- /dev/null +++ b/common/djangoapps/util/tests/test_codejail_includes.py @@ -0,0 +1,26 @@ +""" +Tests for codejail-includes package. +""" + +import unittest + +import eia +import loncapa +from verifiers import draganddrop + + +class TestCodeJailIncludes(unittest.TestCase): + """ tests for codejail includes""" + + def test_loncapa(self): + random_integer = loncapa.lc_random(2, 60, 5) + assert random_integer <= 60 + assert random_integer >= 2 + + def test_nested_list_and_list1(self): + assert draganddrop.PositionsCompare([[1, 2], 40]) == draganddrop.PositionsCompare([1, 3]) + + def test_Eia(self): + # Test cases. All of these should return True + assert eia.iseia(100) # 100 ohm resistor is EIA + assert not eia.iseia(101) # 101 is not diff --git a/common/lib/capa/capa/safe_exec/README.rst b/common/lib/capa/capa/safe_exec/README.rst index 9a28480caf..ef12f45af6 100644 --- a/common/lib/capa/capa/safe_exec/README.rst +++ b/common/lib/capa/capa/safe_exec/README.rst @@ -15,18 +15,12 @@ CodeJail`__, with a few customized tweaks. __ https://github.com/edx/codejail/blob/master/README.rst -1. At the instruction to install packages into the sandboxed code, you'll +1. At the instruction to install packages into the sandboxed code, you'll need to install the requirements from requirements/edx-sandbox:: $ pip install -r requirements/edx-sandbox/base.txt -2. At the instruction to create the AppArmor profile, you'll need a line in - the profile for the sandbox packages. is the full path to - your edx_platform repo:: - - /common/lib/sandbox-packages/** r, - -3. You can configure resource limits in settings.py. A CODE_JAIL setting is +2. You can configure resource limits in settings.py. A CODE_JAIL setting is available, a dictionary. The "limits" key lets you adjust the limits for CPU time, real time, and memory use. Setting any of them to zero disables that limit:: diff --git a/common/lib/sandbox-packages/README b/common/lib/sandbox-packages/README deleted file mode 100644 index 706998b08e..0000000000 --- a/common/lib/sandbox-packages/README +++ /dev/null @@ -1 +0,0 @@ -This directory is in the Python path for sandboxed Python execution. diff --git a/common/lib/sandbox-packages/eia.py b/common/lib/sandbox-packages/eia.py deleted file mode 100644 index f8c2da59f1..0000000000 --- a/common/lib/sandbox-packages/eia.py +++ /dev/null @@ -1,112 +0,0 @@ -""" -Standard resistor values. - -Commonly used for verifying electronic components in circuit classes are -standard values, or conversely, for generating realistic component -values in parameterized problems. For details, see: - -http://en.wikipedia.org/wiki/Electronic_color_code -""" - - -# pylint: disable=invalid-name -# r is standard name for a resistor. We would like to use it as such. - -import math -import numbers - -E6 = [10, 15, 22, 33, 47, 68] - -E12 = [10, 12, 15, 18, 22, 27, 33, 39, 47, 56, 68, 82] -E24 = [10, 12, 15, 18, 22, 27, 33, 39, 47, 56, 68, 82, 11, 13, 16, 20, - 24, 30, 36, 43, 51, 62, 75, 91] - -E48 = [100, 121, 147, 178, 215, 261, 316, 383, 464, 562, 681, 825, 105, - 127, 154, 187, 226, 274, 332, 402, 487, 590, 715, 866, 110, 133, - 162, 196, 237, 287, 348, 422, 511, 619, 750, 909, 115, 140, 169, - 205, 249, 301, 365, 442, 536, 649, 787, 953] - -E96 = [100, 121, 147, 178, 215, 261, 316, 383, 464, 562, 681, 825, 102, - 124, 150, 182, 221, 267, 324, 392, 475, 576, 698, 845, 105, 127, - 154, 187, 226, 274, 332, 402, 487, 590, 715, 866, 107, 130, 158, - 191, 232, 280, 340, 412, 499, 604, 732, 887, 110, 133, 162, 196, - 237, 287, 348, 422, 511, 619, 750, 909, 113, 137, 165, 200, 243, - 294, 357, 432, 523, 634, 768, 931, 115, 140, 169, 205, 249, 301, - 365, 442, 536, 649, 787, 953, 118, 143, 174, 210, 255, 309, 374, - 453, 549, 665, 806, 976] - -E192 = [100, 121, 147, 178, 215, 261, 316, 383, 464, 562, 681, 825, 101, - 123, 149, 180, 218, 264, 320, 388, 470, 569, 690, 835, 102, 124, - 150, 182, 221, 267, 324, 392, 475, 576, 698, 845, 104, 126, 152, - 184, 223, 271, 328, 397, 481, 583, 706, 856, 105, 127, 154, 187, - 226, 274, 332, 402, 487, 590, 715, 866, 106, 129, 156, 189, 229, - 277, 336, 407, 493, 597, 723, 876, 107, 130, 158, 191, 232, 280, - 340, 412, 499, 604, 732, 887, 109, 132, 160, 193, 234, 284, 344, - 417, 505, 612, 741, 898, 110, 133, 162, 196, 237, 287, 348, 422, - 511, 619, 750, 909, 111, 135, 164, 198, 240, 291, 352, 427, 517, - 626, 759, 920, 113, 137, 165, 200, 243, 294, 357, 432, 523, 634, - 768, 931, 114, 138, 167, 203, 246, 298, 361, 437, 530, 642, 777, - 942, 115, 140, 169, 205, 249, 301, 365, 442, 536, 649, 787, 953, - 117, 142, 172, 208, 252, 305, 370, 448, 542, 657, 796, 965, 118, - 143, 174, 210, 255, 309, 374, 453, 549, 665, 806, 976, 120, 145, - 176, 213, 258, 312, 379, 459, 556, 673, 816, 988] - - -def iseia(r, valid_types=(E6, E12, E24)): - ''' - Check if a component is a valid EIA value. - - By default, check 5% component values - ''' - - # Step 1: Discount things which are not numbers - if not isinstance(r, numbers.Number) or \ - r < 0 or \ - math.isnan(r) or \ - math.isinf(r): - return False - - # Special case: 0 is an okay resistor - if r == 0: - return True - - # Step 2: Move into the range [100, 1000) - while r < 100: - r = r * 10 - while r >= 1000: - r = r / 10 - - # Step 3: Discount things which are not integers, and cast to int - if abs(r - round(r)) > 0.01: - return False - r = int(round(r)) - - # Step 4: Check if we're a valid EIA value - for type_list in valid_types: - if r in type_list: - return True - if int(r / 10.) in type_list and (r % 10) == 0: - return True - - return False - -if __name__ == '__main__': - # Test cases. All of these should return True - print(iseia(100)) # 100 ohm resistor is EIA - print(not iseia(101)) # 101 is not - print(not iseia(100.3)) # Floating point close to EIA is not EIA - print(iseia(100.001)) # But within floating point error is - print(iseia(1e5)) # We handle big numbers well - print(iseia(2200)) # We handle middle-of-the-list well - # We can handle 1% components correctly; 2.2k is EIA24, but not EIA48. - print(not iseia(2200, (E48, E96, E192))) - print(iseia(5490e2, (E48, E96, E192))) - print(iseia(2200)) - print(not iseia(5490e2)) - print(iseia(1e-5)) # We handle little numbers well - print(not iseia("Hello")) # Junk handled okay - print(not iseia(float('NaN'))) - print(not iseia(-1)) - print(not iseia(iseia)) - print(not iseia(float('Inf'))) - print(iseia(0)) # Corner case. 0 is a standard resistor value. diff --git a/common/lib/sandbox-packages/loncapa/__init__.py b/common/lib/sandbox-packages/loncapa/__init__.py deleted file mode 100644 index 3d613e04de..0000000000 --- a/common/lib/sandbox-packages/loncapa/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/python # lint-amnesty, pylint: disable=missing-module-docstring - -from .loncapa_check import * # lint-amnesty, pylint: disable=redefined-builtin diff --git a/common/lib/sandbox-packages/loncapa/loncapa_check.py b/common/lib/sandbox-packages/loncapa/loncapa_check.py deleted file mode 100644 index 80a0a51545..0000000000 --- a/common/lib/sandbox-packages/loncapa/loncapa_check.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/python # lint-amnesty, pylint: disable=missing-module-docstring -# -# File: mitx/lib/loncapa/loncapa_check.py -# -# Python functions which duplicate the standard comparison functions available to LON-CAPA problems. -# Used in translating LON-CAPA problems to i4x problem specification language. - - -import math -import random -from six.moves import range - - -def lc_random(lower, upper, stepsize): - ''' - like random.randrange but lower and upper can be non-integer - ''' - nstep = int((upper - lower) / (1.0 * stepsize)) - choices = [lower + x * stepsize for x in range(nstep)] - return random.choice(choices) - - -def lc_choose(index, *args): - ''' - return args[index] - ''' - try: - return args[int(index) - 1] - except Exception as err: # lint-amnesty, pylint: disable=broad-except, unused-variable - pass - if len(args): # lint-amnesty, pylint: disable=len-as-condition - return args[0] - raise Exception( - "loncapa_check.lc_choose error, index={index}, args={args}".format( - index=index, - args=args, - ) - ) - -deg2rad = math.pi / 180.0 -rad2deg = 180.0 / math.pi diff --git a/common/lib/sandbox-packages/setup.py b/common/lib/sandbox-packages/setup.py deleted file mode 100644 index 3a02c854bc..0000000000 --- a/common/lib/sandbox-packages/setup.py +++ /dev/null @@ -1,17 +0,0 @@ -# lint-amnesty, pylint: disable=missing-module-docstring - -from setuptools import setup - -setup( - name="sandbox-packages", - version="0.1.1", - packages=[ - "loncapa", - "verifiers", - ], - py_modules=[ - "eia", - ], - install_requires=[ - ], -) diff --git a/common/lib/sandbox-packages/verifiers/__init__.py b/common/lib/sandbox-packages/verifiers/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/common/lib/sandbox-packages/verifiers/draganddrop.py b/common/lib/sandbox-packages/verifiers/draganddrop.py deleted file mode 100644 index 9d41366c58..0000000000 --- a/common/lib/sandbox-packages/verifiers/draganddrop.py +++ /dev/null @@ -1,433 +0,0 @@ -""" Grader of drag and drop input. - -Client side behavior: user can drag and drop images from list on base image. - - - Then json returned from client is: - { - "draggable": [ - { "image1": "t1" }, - { "ant": "t2" }, - { "molecule": "t3" }, - ] -} -values are target names. - -or: - { - "draggable": [ - { "image1": "[10, 20]" }, - { "ant": "[30, 40]" }, - { "molecule": "[100, 200]" }, - ] -} -values are (x, y) coordinates of centers of dragged images. -""" - - -import json -import six -from six.moves import zip - - -def flat_user_answer(user_answer): - """ - Convert nested `user_answer` to flat format. - - {'up': {'first': {'p': 'p_l'}}} - - to - - {'up': 'p_l[p][first]'} - """ - - def parse_user_answer(answer): - key = list(answer.keys())[0] - value = list(answer.values())[0] - if isinstance(value, dict): - - # Make complex value: - # Example: - # Create like 'p_l[p][first]' from {'first': {'p': 'p_l'} - complex_value_list = [] - v_value = value - while isinstance(v_value, dict): - v_key = list(v_value.keys())[0] - v_value = list(v_value.values())[0] - complex_value_list.append(v_key) - - complex_value = '{0}'.format(v_value) - for i in reversed(complex_value_list): - complex_value = '{0}[{1}]'.format(complex_value, i) - - res = {key: complex_value} - return res - else: - return answer - - result = [] - for answer in user_answer: - parse_answer = parse_user_answer(answer) - result.append(parse_answer) - - return result - - -class PositionsCompare(list): - """ Class for comparing positions. - - Args: - list or string:: - "abc" - target - [10, 20] - list of integers - [[10, 20], 200] list of list and integer - - """ - def __eq__(self, other): - """ Compares two arguments. - - Default lists behavior is conversion of string "abc" to list - ["a", "b", "c"]. We will use that. - - If self or other is empty - returns False. - - Args: - self, other: str, unicode, list, int, float - - Returns: bool - """ - # checks if self or other is not empty list (empty lists = false) - if not self or not other: - return False - - if (isinstance(self[0], (list, int, float)) and - isinstance(other[0], (list, int, float))): - return self.coordinate_positions_compare(other) - - elif (isinstance(self[0], (six.text_type, str)) and - isinstance(other[0], (six.text_type, str))): - return ''.join(self) == ''.join(other) - else: # improper argument types: no (float / int or lists of list - #and float / int pair) or two string / unicode lists pair - return False - - def __ne__(self, other): - return not self.__eq__(other) - - def coordinate_positions_compare(self, other, r=10): - """ Checks if self is equal to other inside radius of forgiveness - (default 10 px). - - Args: - self, other: [x, y] or [[x, y], r], where r is radius of - forgiveness; - x, y, r: int - - Returns: bool. - """ - # get max radius of forgiveness - if isinstance(self[0], list): # [(x, y), r] case - r = max(self[1], r) - x1, y1 = self[0] - else: - x1, y1 = self - - if isinstance(other[0], list): # [(x, y), r] case - r = max(other[1], r) - x2, y2 = other[0] - else: - x2, y2 = other - - if (x2 - x1) ** 2 + (y2 - y1) ** 2 > r * r: - return False - - return True - - -class DragAndDrop(object): - """ Grader class for drag and drop inputtype. - """ - - def grade(self): - ''' Grader user answer. - - Checks if every draggable isplaced on proper target or on proper - coordinates within radius of forgiveness (default is 10). - - Returns: bool. - ''' - for draggable in self.excess_draggables: - if self.excess_draggables[draggable]: - return False # user answer has more draggables than correct answer - - # Number of draggables in user_groups may be differ that in - # correct_groups, that is incorrect, except special case with 'number' - for index, draggable_ids in enumerate(self.correct_groups): - # 'number' rule special case - # for reusable draggables we may get in self.user_groups - # {'1': [u'2', u'2', u'2'], '0': [u'1', u'1'], '2': [u'3']} - # if '+number' is in rule - do not remove duplicates and strip - # '+number' from rule - current_rule = list(self.correct_positions[index].keys())[0] - if 'number' in current_rule: - rule_values = self.correct_positions[index][current_rule] - # clean rule, do not do clean duplicate items - self.correct_positions[index].pop(current_rule, None) - parsed_rule = current_rule.replace('+', '').replace('number', '') - self.correct_positions[index][parsed_rule] = rule_values - else: # remove dublicates - self.user_groups[index] = list(set(self.user_groups[index])) - - if sorted(draggable_ids) != sorted(self.user_groups[index]): - return False - - # Check that in every group, for rule of that group, user positions of - # every element are equal with correct positions - for index, _ in enumerate(self.correct_groups): - rules_executed = 0 - for rule in ('exact', 'anyof', 'unordered_equal'): - # every group has only one rule - if self.correct_positions[index].get(rule, None): - rules_executed += 1 - if not self.compare_positions( - self.correct_positions[index][rule], - self.user_positions[index]['user'], flag=rule): - return False - if not rules_executed: # no correct rules for current group - # probably xml content mistake - wrong rules names - return False - - return True - - def compare_positions(self, correct, user, flag): - """ Compares two lists of positions with flag rules. Order of - correct/user arguments is matter only in 'anyof' flag. - - Rules description: - - 'exact' means 1-1 ordered relationship:: - - [el1, el2, el3] is 'exact' equal to [el5, el6, el7] when - el1 == el5, el2 == el6, el3 == el7. - Equality function is custom, see below. - - - 'anyof' means subset relationship:: - - user = [el1, el2] is 'anyof' equal to correct = [el1, el2, el3] - when - set(user) <= set(correct). - - 'anyof' is ordered relationship. It always checks if user - is subset of correct - - Equality function is custom, see below. - - Examples: - - - many draggables per position: - user ['1', '2', '2', '2'] is 'anyof' equal to ['1', '2', '3'] - - - draggables can be placed in any order: - user ['1', '2', '3', '4'] is 'anyof' equal to ['4', '2', '1', 3'] - - 'unordered_equal' is same as 'exact' but disregards on order - - Equality functions: - - Equality functon depends on type of element. They declared in - PositionsCompare class. For position like targets - ids ("t1", "t2", etc..) it is string equality function. For coordinate - positions ([1, 2] or [[1, 2], 15]) it is coordinate_positions_compare - function (see docstrings in PositionsCompare class) - - Args: - correst, user: lists of positions - - Returns: True if within rule lists are equal, otherwise False. - """ - if flag == 'exact': - if len(correct) != len(user): - return False - for el1, el2 in zip(correct, user): - if PositionsCompare(el1) != PositionsCompare(el2): - return False - - if flag == 'anyof': - for u_el in user: - for c_el in correct: - if PositionsCompare(u_el) == PositionsCompare(c_el): - break - else: - # General: the else is executed after the for, - # only if the for terminates normally (not by a break) - - # In this case, 'for' is terminated normally if every element - # from 'correct' list isn't equal to concrete element from - # 'user' list. So as we found one element from 'user' list, - # that not in 'correct' list - we return False - return False - - if flag == 'unordered_equal': - if len(correct) != len(user): - return False - temp = correct[:] - for u_el in user: - for c_el in temp: - if PositionsCompare(u_el) == PositionsCompare(c_el): - temp.remove(c_el) - break - else: - # same as upper - if we found element from 'user' list, - # that not in 'correct' list - we return False. - return False - - return True - - def __init__(self, correct_answer, user_answer): - """ Populates DragAndDrop variables from user_answer and correct_answer. - If correct_answer is dict, converts it to list. - Correct answer in dict form is simple structure for fast and simple - grading. Example of correct answer dict example:: - - correct_answer = {'name4': 't1', - 'name_with_icon': 't1', - '5': 't2', - '7': 't2'} - - It is draggable_name: dragable_position mapping. - - Advanced form converted from simple form uses 'exact' rule - for matching. - - Correct answer in list form is designed for advanced cases:: - - correct_answers = [ - { - 'draggables': ['1', '2', '3', '4', '5', '6'], - 'targets': [ - 's_left', 's_right', 's_sigma', 's_sigma_star', 'p_pi_1', 'p_pi_2'], - 'rule': 'anyof'}, - { - 'draggables': ['7', '8', '9', '10'], - 'targets': ['p_left_1', 'p_left_2', 'p_right_1', 'p_right_2'], - 'rule': 'anyof' - } - ] - - Advanced answer in list form is list of dicts, and every dict must have - 3 keys: 'draggables', 'targets' and 'rule'. 'Draggables' value is - list of draggables ids, 'targes' values are list of targets ids, 'rule' - value one of 'exact', 'anyof', 'unordered_equal', 'anyof+number', - 'unordered_equal+number' - - Advanced form uses "all dicts must match with their rule" logic. - - Same draggable cannot appears more that in one dict. - - Behavior is more widely explained in sphinx documentation. - - Args: - user_answer: json - correct_answer: dict or list - """ - - self.correct_groups = [] # Correct groups from xml. - self.correct_positions = [] # Correct positions for comparing. - self.user_groups = [] # Will be populated from user answer. - self.user_positions = [] # Will be populated from user answer. - - # Convert from dict answer format to list format. - if isinstance(correct_answer, dict): - tmp = [] - for key in sorted(correct_answer.keys()): - value = correct_answer[key] - tmp.append({ - 'draggables': [key], - 'targets': [value], - 'rule': 'exact'}) - correct_answer = tmp - - # Convert string `user_answer` to object. - user_answer = json.loads(user_answer) - - # This dictionary will hold a key for each draggable the user placed on - # the image. The value is True if that draggable is not mentioned in any - # correct_answer entries. If the draggable is mentioned in at least one - # correct_answer entry, the value is False. - # default to consider every user answer excess until proven otherwise. - self.excess_draggables = dict( - (list(users_draggable.keys())[0], True) - for users_draggable in user_answer - ) - - # Convert nested `user_answer` to flat format. - user_answer = flat_user_answer(user_answer) - - # Create identical data structures from user answer and correct answer. - for answer in correct_answer: - user_groups_data = [] - user_positions_data = [] - for draggable_dict in user_answer: - # Draggable_dict is 1-to-1 {draggable_name: position}. - draggable_name = list(draggable_dict.keys())[0] - if draggable_name in answer['draggables']: - user_groups_data.append(draggable_name) - user_positions_data.append( - draggable_dict[draggable_name] - ) - # proved that this is not excess - self.excess_draggables[draggable_name] = False - - self.correct_groups.append(answer['draggables']) - self.correct_positions.append({answer['rule']: answer['targets']}) - self.user_groups.append(user_groups_data) - self.user_positions.append({'user': user_positions_data}) - - -def grade(user_input, correct_answer): - """ Creates DragAndDrop instance from user_input and correct_answer and - calls DragAndDrop.grade for grading. - - Supports two interfaces for correct_answer: dict and list. - - Args: - user_input: json. Format:: - - { "draggables": - [{"1": [10, 10]}, {"name_with_icon": [20, 20]}]}' - - or - - {"draggables": [{"1": "t1"}, \ - {"name_with_icon": "t2"}]} - - correct_answer: dict or list. - - Dict form:: - - {'1': 't1', 'name_with_icon': 't2'} - - or - - {'1': '[10, 10]', 'name_with_icon': '[[10, 10], 20]'} - - List form:: - - correct_answer = [ - { - 'draggables': ['l3_o', 'l10_o'], - 'targets': ['t1_o', 't9_o'], - 'rule': 'anyof' - }, - { - 'draggables': ['l1_c', 'l8_c'], - 'targets': ['t5_c', 't6_c'], - 'rule': 'anyof' - } - ] - - Returns: bool - """ - return DragAndDrop(correct_answer=correct_answer, - user_answer=user_input).grade() diff --git a/common/lib/sandbox-packages/verifiers/tests_draganddrop.py b/common/lib/sandbox-packages/verifiers/tests_draganddrop.py deleted file mode 100644 index 03bea08587..0000000000 --- a/common/lib/sandbox-packages/verifiers/tests_draganddrop.py +++ /dev/null @@ -1,843 +0,0 @@ -# lint-amnesty, pylint: disable=missing-module-docstring - -import json -import unittest - -from . import draganddrop - -from .draganddrop import PositionsCompare - - -class Test_PositionsCompare(unittest.TestCase): - """ describe""" - - def test_nested_list_and_list1(self): - assert PositionsCompare([[1, 2], 40]) == PositionsCompare([1, 3]) - - def test_nested_list_and_list2(self): - assert PositionsCompare([1, 12]) != PositionsCompare([1, 1]) - - def test_list_and_list1(self): - assert PositionsCompare([[1, 2], 12]) != PositionsCompare([1, 15]) - - def test_list_and_list2(self): - assert PositionsCompare([1, 11]) == PositionsCompare([1, 1]) - - def test_numerical_list_and_string_list(self): - assert PositionsCompare([1, 2]) != PositionsCompare(['1']) - - def test_string_and_string_list1(self): - assert PositionsCompare('1') == PositionsCompare(['1']) - - def test_string_and_string_list2(self): - assert PositionsCompare('abc') == PositionsCompare('abc') - - def test_string_and_string_list3(self): - assert PositionsCompare('abd') != PositionsCompare('abe') - - def test_float_and_string(self): - assert PositionsCompare([3.5, 5.7]) != PositionsCompare(['1']) - - def test_floats_and_ints(self): - assert PositionsCompare([3.5, 4.5]) == PositionsCompare([5, 7]) - - -class Test_DragAndDrop_Grade(unittest.TestCase): # lint-amnesty, pylint: disable=missing-class-docstring - - def test_targets_are_draggable_1(self): - user_input = json.dumps([ - {'p': 'p_l'}, - {'up': {'first': {'p': 'p_l'}}} - ]) - - correct_answer = [ - { - 'draggables': ['p'], - 'targets': ['p_l', 'p_r'], - 'rule': 'anyof' - }, - { - 'draggables': ['up'], - 'targets': [ - 'p_l[p][first]' - ], - 'rule': 'anyof' - } - ] - assert draganddrop.grade(user_input, correct_answer) - - def test_targets_are_draggable_2(self): - user_input = json.dumps([ - {'p': 'p_l'}, - {'p': 'p_r'}, - {'s': 's_l'}, - {'s': 's_r'}, - {'up': {'1': {'p': 'p_l'}}}, - {'up': {'3': {'p': 'p_l'}}}, - {'up': {'1': {'p': 'p_r'}}}, - {'up': {'3': {'p': 'p_r'}}}, - {'up_and_down': {'1': {'s': 's_l'}}}, - {'up_and_down': {'1': {'s': 's_r'}}} - ]) - - correct_answer = [ - { - 'draggables': ['p'], - 'targets': ['p_l', 'p_r'], - 'rule': 'unordered_equal' - }, - { - 'draggables': ['s'], - 'targets': ['s_l', 's_r'], - 'rule': 'unordered_equal' - }, - { - 'draggables': ['up_and_down'], - 'targets': ['s_l[s][1]', 's_r[s][1]'], - 'rule': 'unordered_equal' - }, - { - 'draggables': ['up'], - 'targets': [ - 'p_l[p][1]', - 'p_l[p][3]', - 'p_r[p][1]', - 'p_r[p][3]', - ], - 'rule': 'unordered_equal' - } - ] - assert draganddrop.grade(user_input, correct_answer) - - def test_targets_are_draggable_2_manual_parsing(self): - user_input = json.dumps([ - {'up': 'p_l[p][1]'}, - {'p': 'p_l'}, - {'up': 'p_l[p][3]'}, - {'up': 'p_r[p][1]'}, - {'p': 'p_r'}, - {'up': 'p_r[p][3]'}, - {'up_and_down': 's_l[s][1]'}, - {'s': 's_l'}, - {'up_and_down': 's_r[s][1]'}, - {'s': 's_r'} - ]) - - correct_answer = [ - { - 'draggables': ['p'], - 'targets': ['p_l', 'p_r'], - 'rule': 'unordered_equal' - }, - { - 'draggables': ['s'], - 'targets': ['s_l', 's_r'], - 'rule': 'unordered_equal' - }, - { - 'draggables': ['up_and_down'], - 'targets': ['s_l[s][1]', 's_r[s][1]'], - 'rule': 'unordered_equal' - }, - { - 'draggables': ['up'], - 'targets': [ - 'p_l[p][1]', - 'p_l[p][3]', - 'p_r[p][1]', - 'p_r[p][3]', - ], - 'rule': 'unordered_equal' - } - ] - assert draganddrop.grade(user_input, correct_answer) - - def test_targets_are_draggable_3_nested(self): - user_input = json.dumps([ - {'molecule': 'left_side_tagret'}, - {'molecule': 'right_side_tagret'}, - {'p': {'p_target': {'molecule': 'left_side_tagret'}}}, - {'p': {'p_target': {'molecule': 'right_side_tagret'}}}, - {'s': {'s_target': {'molecule': 'left_side_tagret'}}}, - {'s': {'s_target': {'molecule': 'right_side_tagret'}}}, - {'up': {'1': {'p': {'p_target': {'molecule': 'left_side_tagret'}}}}}, - {'up': {'3': {'p': {'p_target': {'molecule': 'left_side_tagret'}}}}}, - {'up': {'1': {'p': {'p_target': {'molecule': 'right_side_tagret'}}}}}, - {'up': {'3': {'p': {'p_target': {'molecule': 'right_side_tagret'}}}}}, - {'up_and_down': {'1': {'s': {'s_target': {'molecule': 'left_side_tagret'}}}}}, - {'up_and_down': {'1': {'s': {'s_target': {'molecule': 'right_side_tagret'}}}}} - ]) - - correct_answer = [ - { - 'draggables': ['molecule'], - 'targets': ['left_side_tagret', 'right_side_tagret'], - 'rule': 'unordered_equal' - }, - { - 'draggables': ['p'], - 'targets': [ - 'left_side_tagret[molecule][p_target]', - 'right_side_tagret[molecule][p_target]', - ], - 'rule': 'unordered_equal' - }, - { - 'draggables': ['s'], - 'targets': [ - 'left_side_tagret[molecule][s_target]', - 'right_side_tagret[molecule][s_target]', - ], - 'rule': 'unordered_equal' - }, - { - 'draggables': ['up_and_down'], - 'targets': [ - 'left_side_tagret[molecule][s_target][s][1]', - 'right_side_tagret[molecule][s_target][s][1]', - ], - 'rule': 'unordered_equal' - }, - { - 'draggables': ['up'], - 'targets': [ - 'left_side_tagret[molecule][p_target][p][1]', - 'left_side_tagret[molecule][p_target][p][3]', - 'right_side_tagret[molecule][p_target][p][1]', - 'right_side_tagret[molecule][p_target][p][3]', - ], - 'rule': 'unordered_equal' - } - ] - assert draganddrop.grade(user_input, correct_answer) - - def test_targets_are_draggable_4_real_example(self): - user_input = json.dumps([ - {'single_draggable': 's_l'}, - {'single_draggable': 's_r'}, - {'single_draggable': 'p_sigma'}, - {'single_draggable': 'p_sigma*'}, - {'single_draggable': 's_sigma'}, - {'single_draggable': 's_sigma*'}, - {'double_draggable': 'p_pi*'}, - {'double_draggable': 'p_pi'}, - {'triple_draggable': 'p_l'}, - {'triple_draggable': 'p_r'}, - {'up': {'1': {'triple_draggable': 'p_l'}}}, - {'up': {'2': {'triple_draggable': 'p_l'}}}, - {'up': {'2': {'triple_draggable': 'p_r'}}}, - {'up': {'3': {'triple_draggable': 'p_r'}}}, - {'up_and_down': {'1': {'single_draggable': 's_l'}}}, - {'up_and_down': {'1': {'single_draggable': 's_r'}}}, - {'up_and_down': {'1': {'single_draggable': 's_sigma'}}}, - {'up_and_down': {'1': {'single_draggable': 's_sigma*'}}}, - {'up_and_down': {'1': {'double_draggable': 'p_pi'}}}, - {'up_and_down': {'2': {'double_draggable': 'p_pi'}}} - ]) - - # 10 targets: - # s_l, s_r, p_l, p_r, s_sigma, s_sigma*, p_pi, p_sigma, p_pi*, p_sigma* - # - # 3 draggable objects, which have targets (internal target ids - 1, 2, 3): - # single_draggable, double_draggable, triple_draggable - # - # 2 draggable objects: - # up, up_and_down - correct_answer = [ - { - 'draggables': ['triple_draggable'], - 'targets': ['p_l', 'p_r'], - 'rule': 'unordered_equal' - }, - { - 'draggables': ['double_draggable'], - 'targets': ['p_pi', 'p_pi*'], - 'rule': 'unordered_equal' - }, - { - 'draggables': ['single_draggable'], - 'targets': ['s_l', 's_r', 's_sigma', 's_sigma*', 'p_sigma', 'p_sigma*'], - 'rule': 'unordered_equal' - }, - { - 'draggables': ['up'], - 'targets': [ - 'p_l[triple_draggable][1]', - 'p_l[triple_draggable][2]', - 'p_r[triple_draggable][2]', - 'p_r[triple_draggable][3]', - ], - 'rule': 'unordered_equal' - }, - { - 'draggables': ['up_and_down'], - 'targets': [ - 's_l[single_draggable][1]', - 's_r[single_draggable][1]', - 's_sigma[single_draggable][1]', - 's_sigma*[single_draggable][1]', - 'p_pi[double_draggable][1]', - 'p_pi[double_draggable][2]', - ], - 'rule': 'unordered_equal' - }, - - ] - assert draganddrop.grade(user_input, correct_answer) - - def test_targets_true(self): - user_input = '[{"1": "t1"}, \ - {"name_with_icon": "t2"}]' - correct_answer = {'1': 't1', 'name_with_icon': 't2'} - assert draganddrop.grade(user_input, correct_answer) - - def test_expect_no_actions_wrong(self): - user_input = '[{"1": "t1"}, \ - {"name_with_icon": "t2"}]' - correct_answer = [] - assert not draganddrop.grade(user_input, correct_answer) - - def test_expect_no_actions_right(self): - user_input = '[]' - correct_answer = [] - assert draganddrop.grade(user_input, correct_answer) - - def test_targets_false(self): - user_input = '[{"1": "t1"}, \ - {"name_with_icon": "t2"}]' - correct_answer = {'1': 't3', 'name_with_icon': 't2'} - assert not draganddrop.grade(user_input, correct_answer) - - def test_multiple_images_per_target_true(self): - user_input = '[{"1": "t1"}, {"name_with_icon": "t2"}, \ - {"2": "t1"}]' - correct_answer = {'1': 't1', 'name_with_icon': 't2', '2': 't1'} - assert draganddrop.grade(user_input, correct_answer) - - def test_multiple_images_per_target_false(self): - user_input = '[{"1": "t1"}, {"name_with_icon": "t2"}, \ - {"2": "t1"}]' - correct_answer = {'1': 't2', 'name_with_icon': 't2', '2': 't1'} - assert not draganddrop.grade(user_input, correct_answer) - - def test_targets_and_positions(self): - user_input = '[{"1": [10,10]}, \ - {"name_with_icon": [[10,10],4]}]' - correct_answer = {'1': [10, 10], 'name_with_icon': [[10, 10], 4]} - assert draganddrop.grade(user_input, correct_answer) - - def test_position_and_targets(self): - user_input = '[{"1": "t1"}, {"name_with_icon": "t2"}]' - correct_answer = {'1': 't1', 'name_with_icon': 't2'} - assert draganddrop.grade(user_input, correct_answer) - - def test_positions_exact(self): - user_input = '[{"1": [10, 10]}, {"name_with_icon": [20, 20]}]' - correct_answer = {'1': [10, 10], 'name_with_icon': [20, 20]} - assert draganddrop.grade(user_input, correct_answer) - - def test_positions_false(self): - user_input = '[{"1": [10, 10]}, {"name_with_icon": [20, 20]}]' - correct_answer = {'1': [25, 25], 'name_with_icon': [20, 20]} - assert not draganddrop.grade(user_input, correct_answer) - - def test_positions_true_in_radius(self): - user_input = '[{"1": [10, 10]}, {"name_with_icon": [20, 20]}]' - correct_answer = {'1': [14, 14], 'name_with_icon': [20, 20]} - assert draganddrop.grade(user_input, correct_answer) - - def test_positions_true_in_manual_radius(self): - user_input = '[{"1": [10, 10]}, {"name_with_icon": [20, 20]}]' - correct_answer = {'1': [[40, 10], 30], 'name_with_icon': [20, 20]} - assert draganddrop.grade(user_input, correct_answer) - - def test_positions_false_in_manual_radius(self): - user_input = '[{"1": [10, 10]}, {"name_with_icon": [20, 20]}]' - correct_answer = {'1': [[40, 10], 29], 'name_with_icon': [20, 20]} - assert not draganddrop.grade(user_input, correct_answer) - - def test_correct_answer_not_has_key_from_user_answer(self): - user_input = '[{"1": "t1"}, {"name_with_icon": "t2"}]' - correct_answer = {'3': 't3', 'name_with_icon': 't2'} - assert not draganddrop.grade(user_input, correct_answer) - - def test_anywhere(self): - """Draggables can be places anywhere on base image. - Place grass in the middle of the image and ant in the - right upper corner.""" - user_input = '[{"ant":[610.5,57.449951171875]},\ - {"grass":[322.5,199.449951171875]}]' - - correct_answer = {'grass': [[300, 200], 200], 'ant': [[500, 0], 200]} - assert draganddrop.grade(user_input, correct_answer) - - def test_lcao_correct(self): - """Describe carbon molecule in LCAO-MO""" - user_input = '[{"1":"s_left"}, \ - {"5":"s_right"},{"4":"s_sigma"},{"6":"s_sigma_star"},{"7":"p_left_1"}, \ - {"8":"p_left_2"},{"10":"p_right_1"},{"9":"p_right_2"}, \ - {"2":"p_pi_1"},{"3":"p_pi_2"},{"11":"s_sigma_name"}, \ - {"13":"s_sigma_star_name"},{"15":"p_pi_name"},{"16":"p_pi_star_name"}, \ - {"12":"p_sigma_name"},{"14":"p_sigma_star_name"}]' - - correct_answer = [{ - 'draggables': ['1', '2', '3', '4', '5', '6'], - 'targets': [ - 's_left', 's_right', 's_sigma', 's_sigma_star', 'p_pi_1', 'p_pi_2' - ], - 'rule': 'anyof' - }, { - 'draggables': ['7', '8', '9', '10'], - 'targets': ['p_left_1', 'p_left_2', 'p_right_1', 'p_right_2'], - 'rule': 'anyof' - }, { - 'draggables': ['11', '12'], - 'targets': ['s_sigma_name', 'p_sigma_name'], - 'rule': 'anyof' - }, { - 'draggables': ['13', '14'], - 'targets': ['s_sigma_star_name', 'p_sigma_star_name'], - 'rule': 'anyof' - }, { - 'draggables': ['15'], - 'targets': ['p_pi_name'], - 'rule': 'anyof' - }, { - 'draggables': ['16'], - 'targets': ['p_pi_star_name'], - 'rule': 'anyof' - }] - - assert draganddrop.grade(user_input, correct_answer) - - def test_lcao_extra_element_incorrect(self): - """Describe carbon molecule in LCAO-MO""" - user_input = '[{"1":"s_left"}, \ - {"5":"s_right"},{"4":"s_sigma"},{"6":"s_sigma_star"},{"7":"p_left_1"}, \ - {"8":"p_left_2"},{"17":"p_left_3"},{"10":"p_right_1"},{"9":"p_right_2"}, \ - {"2":"p_pi_1"},{"3":"p_pi_2"},{"11":"s_sigma_name"}, \ - {"13":"s_sigma_star_name"},{"15":"p_pi_name"},{"16":"p_pi_star_name"}, \ - {"12":"p_sigma_name"},{"14":"p_sigma_star_name"}]' - - correct_answer = [{ - 'draggables': ['1', '2', '3', '4', '5', '6'], - 'targets': [ - 's_left', 's_right', 's_sigma', 's_sigma_star', 'p_pi_1', 'p_pi_2' - ], - 'rule': 'anyof' - }, { - 'draggables': ['7', '8', '9', '10'], - 'targets': ['p_left_1', 'p_left_2', 'p_right_1', 'p_right_2'], - 'rule': 'anyof' - }, { - 'draggables': ['11', '12'], - 'targets': ['s_sigma_name', 'p_sigma_name'], - 'rule': 'anyof' - }, { - 'draggables': ['13', '14'], - 'targets': ['s_sigma_star_name', 'p_sigma_star_name'], - 'rule': 'anyof' - }, { - 'draggables': ['15'], - 'targets': ['p_pi_name'], - 'rule': 'anyof' - }, { - 'draggables': ['16'], - 'targets': ['p_pi_star_name'], - 'rule': 'anyof' - }] - - assert not draganddrop.grade(user_input, correct_answer) - - def test_reuse_draggable_no_mupliples(self): - """Test reusable draggables (no mupltiple draggables per target)""" - user_input = '[{"1":"target1"}, \ - {"2":"target2"},{"1":"target3"},{"2":"target4"},{"2":"target5"}, \ - {"3":"target6"}]' - correct_answer = [ - { - 'draggables': ['1'], - 'targets': ['target1', 'target3'], - 'rule': 'anyof' - }, - { - 'draggables': ['2'], - 'targets': ['target2', 'target4', 'target5'], - 'rule': 'anyof' - }, - { - 'draggables': ['3'], - 'targets': ['target6'], - 'rule': 'anyof' - } - ] - assert draganddrop.grade(user_input, correct_answer) - - def test_reuse_draggable_with_mupliples(self): - """Test reusable draggables with mupltiple draggables per target""" - user_input = '[{"1":"target1"}, \ - {"2":"target2"},{"1":"target1"},{"2":"target4"},{"2":"target4"}, \ - {"3":"target6"}]' - correct_answer = [ - { - 'draggables': ['1'], - 'targets': ['target1', 'target3'], - 'rule': 'anyof' - }, - { - 'draggables': ['2'], - 'targets': ['target2', 'target4'], - 'rule': 'anyof' - }, - { - 'draggables': ['3'], - 'targets': ['target6'], - 'rule': 'anyof' - } - ] - assert draganddrop.grade(user_input, correct_answer) - - def test_reuse_many_draggable_with_mupliples(self): - """Test reusable draggables with mupltiple draggables per target""" - user_input = '[{"1":"target1"}, \ - {"2":"target2"},{"1":"target1"},{"2":"target4"},{"2":"target4"}, \ - {"3":"target6"}, {"4": "target3"}, {"5": "target4"}, \ - {"5": "target5"}, {"6": "target2"}]' - correct_answer = [ - { - 'draggables': ['1', '4'], - 'targets': ['target1', 'target3'], - 'rule': 'anyof' - }, - { - 'draggables': ['2', '6'], - 'targets': ['target2', 'target4'], - 'rule': 'anyof' - }, - { - 'draggables': ['5'], - 'targets': ['target4', 'target5'], - 'rule': 'anyof' - }, - { - 'draggables': ['3'], - 'targets': ['target6'], - 'rule': 'anyof' - } - ] - assert draganddrop.grade(user_input, correct_answer) - - def test_reuse_many_draggable_with_mupliples_wrong(self): - """Test reusable draggables with mupltiple draggables per target""" - user_input = '[{"1":"target1"}, \ - {"2":"target2"},{"1":"target1"}, \ - {"2":"target3"}, \ - {"2":"target4"}, \ - {"3":"target6"}, {"4": "target3"}, {"5": "target4"}, \ - {"5": "target5"}, {"6": "target2"}]' - correct_answer = [ - { - 'draggables': ['1', '4'], - 'targets': ['target1', 'target3'], - 'rule': 'anyof' - }, - { - 'draggables': ['2', '6'], - 'targets': ['target2', 'target4'], - 'rule': 'anyof' - }, - { - 'draggables': ['5'], - 'targets': ['target4', 'target5'], - 'rule': 'anyof' - }, - { - 'draggables': ['3'], - 'targets': ['target6'], - 'rule': 'anyof' - }] - assert not draganddrop.grade(user_input, correct_answer) - - def test_label_10_targets_with_a_b_c_false(self): - """Test reusable draggables (no mupltiple draggables per target)""" - user_input = '[{"a":"target1"}, \ - {"b":"target2"},{"c":"target3"},{"a":"target4"},{"b":"target5"}, \ - {"c":"target6"}, {"a":"target7"},{"b":"target8"},{"c":"target9"}, \ - {"a":"target1"}]' - correct_answer = [ - { - 'draggables': ['a'], - 'targets': ['target1', 'target4', 'target7', 'target10'], - 'rule': 'unordered_equal' - }, - { - 'draggables': ['b'], - 'targets': ['target2', 'target5', 'target8'], - 'rule': 'unordered_equal' - }, - { - 'draggables': ['c'], - 'targets': ['target3', 'target6', 'target9'], - 'rule': 'unordered_equal' - } - ] - assert not draganddrop.grade(user_input, correct_answer) - - def test_label_10_targets_with_a_b_c_(self): - """Test reusable draggables (no mupltiple draggables per target)""" - user_input = '[{"a":"target1"}, \ - {"b":"target2"},{"c":"target3"},{"a":"target4"},{"b":"target5"}, \ - {"c":"target6"}, {"a":"target7"},{"b":"target8"},{"c":"target9"}, \ - {"a":"target10"}]' - correct_answer = [ - { - 'draggables': ['a'], - 'targets': ['target1', 'target4', 'target7', 'target10'], - 'rule': 'unordered_equal' - }, - { - 'draggables': ['b'], - 'targets': ['target2', 'target5', 'target8'], - 'rule': 'unordered_equal' - }, - { - 'draggables': ['c'], - 'targets': ['target3', 'target6', 'target9'], - 'rule': 'unordered_equal' - } - ] - assert draganddrop.grade(user_input, correct_answer) - - def test_label_10_targets_with_a_b_c_multiple(self): - """Test reusable draggables (mupltiple draggables per target)""" - user_input = '[{"a":"target1"}, \ - {"b":"target2"},{"c":"target3"},{"b":"target5"}, \ - {"c":"target6"}, {"a":"target7"},{"b":"target8"},{"c":"target9"}, \ - {"a":"target1"}]' - correct_answer = [ - { - 'draggables': ['a', 'a', 'a'], - 'targets': ['target1', 'target4', 'target7', 'target10'], - 'rule': 'anyof+number' - }, - { - 'draggables': ['b', 'b', 'b'], - 'targets': ['target2', 'target5', 'target8'], - 'rule': 'anyof+number' - }, - { - 'draggables': ['c', 'c', 'c'], - 'targets': ['target3', 'target6', 'target9'], - 'rule': 'anyof+number' - } - ] - assert draganddrop.grade(user_input, correct_answer) - - def test_label_10_targets_with_a_b_c_multiple_false(self): - """Test reusable draggables (mupltiple draggables per target)""" - user_input = '[{"a":"target1"}, \ - {"b":"target2"},{"c":"target3"},{"a":"target4"},{"b":"target5"}, \ - {"c":"target6"}, {"a":"target7"},{"b":"target8"},{"c":"target9"}, \ - {"a":"target1"}]' - correct_answer = [ - { - 'draggables': ['a', 'a', 'a'], - 'targets': ['target1', 'target4', 'target7', 'target10'], - 'rule': 'anyof+number' - }, - { - 'draggables': ['b', 'b', 'b'], - 'targets': ['target2', 'target5', 'target8'], - 'rule': 'anyof+number' - }, - { - 'draggables': ['c', 'c', 'c'], - 'targets': ['target3', 'target6', 'target9'], - 'rule': 'anyof+number' - } - ] - assert not draganddrop.grade(user_input, correct_answer) - - def test_label_10_targets_with_a_b_c_reused(self): - """Test a b c in 10 labels reused""" - user_input = '[{"a":"target1"}, \ - {"b":"target2"},{"c":"target3"},{"b":"target5"}, \ - {"c":"target6"}, {"b":"target8"},{"c":"target9"}, \ - {"a":"target10"}]' - correct_answer = [ - { - 'draggables': ['a', 'a'], - 'targets': ['target1', 'target10'], - 'rule': 'unordered_equal+number' - }, - { - 'draggables': ['b', 'b', 'b'], - 'targets': ['target2', 'target5', 'target8'], - 'rule': 'unordered_equal+number' - }, - { - 'draggables': ['c', 'c', 'c'], - 'targets': ['target3', 'target6', 'target9'], - 'rule': 'unordered_equal+number' - } - ] - assert draganddrop.grade(user_input, correct_answer) - - def test_label_10_targets_with_a_b_c_reused_false(self): - """Test a b c in 10 labels reused false""" - user_input = '[{"a":"target1"}, \ - {"b":"target2"},{"c":"target3"},{"b":"target5"}, {"a":"target8"},\ - {"c":"target6"}, {"b":"target8"},{"c":"target9"}, \ - {"a":"target10"}]' - correct_answer = [ - { - 'draggables': ['a', 'a'], - 'targets': ['target1', 'target10'], - 'rule': 'unordered_equal+number' - }, - { - 'draggables': ['b', 'b', 'b'], - 'targets': ['target2', 'target5', 'target8'], - 'rule': 'unordered_equal+number' - }, - { - 'draggables': ['c', 'c', 'c'], - 'targets': ['target3', 'target6', 'target9'], - 'rule': 'unordered_equal+number' - } - ] - assert not draganddrop.grade(user_input, correct_answer) - - def test_mixed_reuse_and_not_reuse(self): - """Test reusable draggables """ - user_input = '[{"a":"target1"}, \ - {"b":"target2"},{"c":"target3"}, {"a":"target4"},\ - {"a":"target5"}]' - correct_answer = [ - { - 'draggables': ['a', 'b'], - 'targets': ['target1', 'target2', 'target4', 'target5'], - 'rule': 'anyof' - }, - { - 'draggables': ['c'], - 'targets': ['target3'], - 'rule': 'exact' - } - ] - assert draganddrop.grade(user_input, correct_answer) - - def test_mixed_reuse_and_not_reuse_number(self): - """Test reusable draggables with number """ - user_input = '[{"a":"target1"}, \ - {"b":"target2"},{"c":"target3"}, {"a":"target4"}]' - correct_answer = [ - { - 'draggables': ['a', 'a', 'b'], - 'targets': ['target1', 'target2', 'target4'], - 'rule': 'anyof+number' - }, - { - 'draggables': ['c'], - 'targets': ['target3'], - 'rule': 'exact' - } - ] - assert draganddrop.grade(user_input, correct_answer) - - def test_mixed_reuse_and_not_reuse_number_false(self): - """Test reusable draggables with numbers, but wrong""" - user_input = '[{"a":"target1"}, \ - {"b":"target2"},{"c":"target3"}, {"a":"target4"}, {"a":"target10"}]' - correct_answer = [ - { - 'draggables': ['a', 'a', 'b'], - 'targets': ['target1', 'target2', 'target4', 'target10'], - 'rule': 'anyof_number' - }, - { - 'draggables': ['c'], - 'targets': ['target3'], - 'rule': 'exact' - } - ] - assert not draganddrop.grade(user_input, correct_answer) - - def test_alternative_correct_answer(self): - user_input = '[{"name_with_icon":"t1"},\ - {"name_with_icon":"t1"},{"name_with_icon":"t1"},{"name4":"t1"}, \ - {"name4":"t1"}]' - correct_answer = [ - {'draggables': ['name4'], 'targets': ['t1', 't1'], 'rule': 'exact'}, - {'draggables': ['name_with_icon'], 'targets': ['t1', 't1', 't1'], - 'rule': 'exact'} - ] - assert draganddrop.grade(user_input, correct_answer) - - -class Test_DragAndDrop_Populate(unittest.TestCase): # lint-amnesty, pylint: disable=missing-class-docstring - - def test_1(self): - correct_answer = {'1': [[40, 10], 29], 'name_with_icon': [20, 20]} - user_input = '[{"1": [10, 10]}, {"name_with_icon": [20, 20]}]' - dnd = draganddrop.DragAndDrop(correct_answer, user_input) - - correct_groups = [['1'], ['name_with_icon']] - correct_positions = [{'exact': [[[40, 10], 29]]}, {'exact': [[20, 20]]}] - user_groups = [['1'], ['name_with_icon']] - user_positions = [{'user': [[10, 10]]}, {'user': [[20, 20]]}] - - assert correct_groups == dnd.correct_groups - assert correct_positions == dnd.correct_positions - assert user_groups == dnd.user_groups - assert user_positions == dnd.user_positions - - -class Test_DraAndDrop_Compare_Positions(unittest.TestCase): # lint-amnesty, pylint: disable=missing-class-docstring - - def test_1(self): - dnd = draganddrop.DragAndDrop({'1': 't1'}, '[{"1": "t1"}]') - assert dnd.compare_positions(correct=[[1, 1], [2, 3]], user=[[2, 3], [1, 1]], flag='anyof') - - def test_2a(self): - dnd = draganddrop.DragAndDrop({'1': 't1'}, '[{"1": "t1"}]') - assert dnd.compare_positions(correct=[[1, 1], [2, 3]], user=[[2, 3], [1, 1]], flag='exact') - - def test_2b(self): - dnd = draganddrop.DragAndDrop({'1': 't1'}, '[{"1": "t1"}]') - assert not dnd.compare_positions(correct=[[1, 1], [2, 3]], user=[[2, 13], [1, 1]], flag='exact') - - def test_3(self): - dnd = draganddrop.DragAndDrop({'1': 't1'}, '[{"1": "t1"}]') - assert not dnd.compare_positions(correct=['a', 'b'], user=['a', 'b', 'c'], flag='anyof') - - def test_4(self): - dnd = draganddrop.DragAndDrop({'1': 't1'}, '[{"1": "t1"}]') - assert dnd.compare_positions(correct=['a', 'b', 'c'], user=['a', 'b'], flag='anyof') - - def test_5(self): - dnd = draganddrop.DragAndDrop({'1': 't1'}, '[{"1": "t1"}]') - assert not dnd.compare_positions(correct=['a', 'b', 'c'], user=['a', 'c', 'b'], flag='exact') - - def test_6(self): - dnd = draganddrop.DragAndDrop({'1': 't1'}, '[{"1": "t1"}]') - assert dnd.compare_positions(correct=['a', 'b', 'c'], user=['a', 'c', 'b'], flag='anyof') - - def test_7(self): - dnd = draganddrop.DragAndDrop({'1': 't1'}, '[{"1": "t1"}]') - assert not dnd.compare_positions(correct=['a', 'b', 'b'], user=['a', 'c', 'b'], flag='anyof') - - -def suite(): # lint-amnesty, pylint: disable=missing-function-docstring - - testcases = [Test_PositionsCompare, - Test_DragAndDrop_Populate, - Test_DragAndDrop_Grade, - Test_DraAndDrop_Compare_Positions] - suites = [] - for testcase in testcases: - suites.append(unittest.TestLoader().loadTestsFromTestCase(testcase)) - return unittest.TestSuite(suites) - -if __name__ == "__main__": - unittest.TextTestRunner(verbosity=2).run(suite()) diff --git a/pavelib/quality.py b/pavelib/quality.py index 1a2d83a4fa..9f233c8a70 100644 --- a/pavelib/quality.py +++ b/pavelib/quality.py @@ -71,10 +71,7 @@ def top_python_dirs(dirname): dirs = os.listdir(subdir) top_dirs.extend(d for d in dirs if os.path.isdir(os.path.join(subdir, d))) - # sandbox-packages module causes F0001: module-not-found error when running pylint - # this will exclude sandbox-packages module from pylint execution - # TODO: upgrade the functionality to run pylint tests on sandbox-packages module too. - modules_to_remove = ['sandbox-packages', '__pycache__'] + modules_to_remove = ['__pycache__'] for module in modules_to_remove: if module in top_dirs: top_dirs.remove(module) diff --git a/requirements/edx-sandbox/py38.in b/requirements/edx-sandbox/py38.in index 9d90dcba97..115c8d98f4 100644 --- a/requirements/edx-sandbox/py38.in +++ b/requirements/edx-sandbox/py38.in @@ -11,12 +11,11 @@ pyparsing # Python Parsing module random2 # Implementation of random module that works identically under Python 2 and 3 scipy # Math, science, and engineering library sympy # Symbolic math library +codejail-includes # CodeJail manages execution of untrusted code in secure sandboxes. # numpy>=1.17.0 caused failures in importing numpy in code-jail environment. # The issue will be investigated and fixed in https://openedx.atlassian.net/browse/BOM-2841. numpy>=1.16.0,<1.17.0 -# Install these packages from the edx-platform working tree -# NOTE: if you change code in these packages, you MUST change the version -# number in its setup.py or the code WILL NOT be installed during deploy. --e common/lib/sandbox-packages + + diff --git a/requirements/edx-sandbox/py38.txt b/requirements/edx-sandbox/py38.txt index 8fde6fc3ff..8e164568f4 100644 --- a/requirements/edx-sandbox/py38.txt +++ b/requirements/edx-sandbox/py38.txt @@ -4,8 +4,6 @@ # # make upgrade # -common/lib/sandbox-packages - # via -r requirements/edx-sandbox/py38.in cffi==1.15.0 # via cryptography chem==1.2.0 @@ -14,6 +12,8 @@ click==8.1.3 # via # -c requirements/edx-sandbox/../constraints.txt # nltk +codejail-includes==1.0.0 + # via -r requirements/edx-sandbox/py38.in cryptography==37.0.2 # via -r requirements/edx-sandbox/py38.in cycler==0.11.0 @@ -82,6 +82,7 @@ scipy==1.7.3 six==1.16.0 # via # chem + # codejail-includes # python-dateutil sympy==1.10.1 # via diff --git a/requirements/edx/base.in b/requirements/edx/base.in index 1c88257651..703fc0ee97 100644 --- a/requirements/edx/base.in +++ b/requirements/edx/base.in @@ -36,6 +36,7 @@ botocore==1.8.17 # via boto3, s3transfer bridgekeeper # Used for determining permissions for courseware. celery # Asynchronous task execution library chem # A helper library for chemistry calculations +codejail-includes # CodeJail manages execution of untrusted code in secure sandboxes. contextlib2 # We need contextlib2.ExitStack so we can stop using contextlib.nested which doesn't exist in python 3 crowdsourcehinter-xblock cryptography # Implementations of assorted cryptography algorithms diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index c86e5eb722..1dce877b6f 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -22,8 +22,6 @@ # via -r requirements/edx/local.in -e git+https://github.com/edx/RateXBlock.git@2.0.1#egg=rate-xblock # via -r requirements/edx/github.in --e common/lib/sandbox-packages - # via -r requirements/edx/local.in -e openedx/core/lib/xblock_builtin/xblock_discussion # via -r requirements/edx/local.in -e git+https://github.com/edx-solutions/xblock-google-drive.git@2d176468e33c0713c911b563f8f65f7cf232f5b6#egg=xblock-google-drive @@ -143,6 +141,8 @@ code-annotations==1.3.0 # via # edx-enterprise # edx-toggles +codejail-includes==1.0.0 + # via -r requirements/edx/base.in contextlib2==21.6.0 # via -r requirements/edx/base.in coreapi==2.3.3 @@ -962,6 +962,7 @@ six==1.16.0 # chem # click-repl # codejail + # codejail-includes # crowdsourcehinter-xblock # edx-ace # edx-auth-backends diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index d8f9de315a..e42a7f83e0 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -22,8 +22,6 @@ # via -r requirements/edx/testing.txt -e git+https://github.com/edx/RateXBlock.git@2.0.1#egg=rate-xblock # via -r requirements/edx/testing.txt --e common/lib/sandbox-packages - # via -r requirements/edx/testing.txt -e openedx/core/lib/xblock_builtin/xblock_discussion # via -r requirements/edx/testing.txt -e git+https://github.com/edx-solutions/xblock-google-drive.git@2d176468e33c0713c911b563f8f65f7cf232f5b6#egg=xblock-google-drive @@ -203,6 +201,8 @@ code-annotations==1.3.0 # edx-enterprise # edx-lint # edx-toggles +codejail-includes==1.0.0 + # via -r requirements/edx/testing.txt contextlib2==21.6.0 # via -r requirements/edx/testing.txt coreapi==2.3.3 @@ -1334,6 +1334,7 @@ six==1.16.0 # chem # click-repl # codejail + # codejail-includes # crowdsourcehinter-xblock # edx-ace # edx-auth-backends diff --git a/requirements/edx/local.in b/requirements/edx/local.in index e68bbadf11..eb0165c473 100644 --- a/requirements/edx/local.in +++ b/requirements/edx/local.in @@ -1,7 +1,6 @@ # Python libraries to install that are local to the edx-platform repo -e . -e common/lib/capa --e common/lib/sandbox-packages -e common/lib/xmodule -e openedx/core/lib/xblock_builtin/xblock_discussion diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 390da4feb0..6b3da93e4a 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -22,8 +22,6 @@ # via -r requirements/edx/base.txt -e git+https://github.com/edx/RateXBlock.git@2.0.1#egg=rate-xblock # via -r requirements/edx/base.txt --e common/lib/sandbox-packages - # via -r requirements/edx/base.txt -e openedx/core/lib/xblock_builtin/xblock_discussion # via -r requirements/edx/base.txt -e git+https://github.com/edx-solutions/xblock-google-drive.git@2d176468e33c0713c911b563f8f65f7cf232f5b6#egg=xblock-google-drive @@ -195,6 +193,8 @@ code-annotations==1.3.0 # edx-enterprise # edx-lint # edx-toggles +codejail-includes==1.0.0 + # via -r requirements/edx/base.txt contextlib2==21.6.0 # via -r requirements/edx/base.txt coreapi==2.3.3 @@ -1261,6 +1261,7 @@ six==1.16.0 # chem # click-repl # codejail + # codejail-includes # crowdsourcehinter-xblock # edx-ace # edx-auth-backends diff --git a/scripts/verify-dunder-init.sh b/scripts/verify-dunder-init.sh index c6cf3134bb..9c9fbb130a 100755 --- a/scripts/verify-dunder-init.sh +++ b/scripts/verify-dunder-init.sh @@ -38,7 +38,7 @@ exclude+='|^common/test/data/?.*$' # * common/lib/xmodule -> EXCLUDE from check. # * common/lib/xmodule/xmodule/modulestore -> INCLUDE in check. exclude+='|^common/lib$' -exclude+='|^common/lib/(capa|sandbox-packages|xmodule)$' +exclude+='|^common/lib/(capa|xmodule)$' # Docs, scripts. exclude+='|^docs/.*$'