From 92c07dc725377f8fe43f591dca21e4b2b6f28bf9 Mon Sep 17 00:00:00 2001 From: Jeroen Hermans Date: Tue, 12 Nov 2019 13:47:19 +0100 Subject: [PATCH 1/3] Test current HOF behaviour --- tests/test_check_function.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_check_function.py b/tests/test_check_function.py index 6bb44d3e..4703b7c2 100644 --- a/tests/test_check_function.py +++ b/tests/test_check_function.py @@ -520,3 +520,22 @@ def test_function_call_in_comparison(code): sct = "Ex().check_function('len')" res = helper.run({"DC_CODE": code, "DC_SOLUTION": code, "DC_SCT": sct}) assert res["correct"] + + +def test_ho_function(): + # TODO: FunctionParser.visit_Call should append something to name to discern HOF calls + # e.g. () if node.func is Func (this should only affect limited exercises) + sct = "Ex().check_function('hof').check_args(0).has_equal_value(override=2)" + + code = """ +def hof(arg1): + def inner(arg2): + return arg1, arg2 + + return inner + +hof(1)(2) + """ + + res = helper.run({"DC_CODE": code, "DC_SOLUTION": code, "DC_SCT": sct}) + assert res["correct"] From 78e780fac334bf654be1dfaf36de906eb1e042c1 Mon Sep 17 00:00:00 2001 From: Jeroen Hermans Date: Fri, 3 Jan 2020 16:59:29 +0100 Subject: [PATCH 2/3] Fix higher order function call parsing --- pythonwhat/parsing.py | 4 ++++ tests/test_check_function.py | 20 ++++++++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/pythonwhat/parsing.py b/pythonwhat/parsing.py index cd9102d4..e017c127 100644 --- a/pythonwhat/parsing.py +++ b/pythonwhat/parsing.py @@ -1,4 +1,6 @@ import ast +import re + from pythonwhat.utils_ast import wrap_in_module from collections.abc import Sequence, Mapping from collections import OrderedDict @@ -326,6 +328,7 @@ def visit_Dict(self, node): def visit_Call(self, node): if self.call_lookup_active: self.visit(node.func) + self.gen_name += "()" else: self.call_lookup_active = True self.visit( @@ -333,6 +336,7 @@ def visit_Call(self, node): ) # Need to visit func to start recording the current function name. if self.gen_name: + self.gen_name = re.sub(r"(?:\(\))+(.)", "\\1", self.gen_name) if self.gen_name not in self.out: self.out[self.gen_name] = [] diff --git a/tests/test_check_function.py b/tests/test_check_function.py index 4703b7c2..85636fc5 100644 --- a/tests/test_check_function.py +++ b/tests/test_check_function.py @@ -522,18 +522,26 @@ def test_function_call_in_comparison(code): assert res["correct"] -def test_ho_function(): - # TODO: FunctionParser.visit_Call should append something to name to discern HOF calls - # e.g. () if node.func is Func (this should only affect limited exercises) - sct = "Ex().check_function('hof').check_args(0).has_equal_value(override=2)" +@pytest.mark.parametrize( + "sct", + [ + "Ex().check_function('numpy.array')", + "Ex().check_function('hof').check_args(0).has_equal_value(override=1)", + "Ex().check_function('hof()').check_args(0).has_equal_value(override=2)", + ], +) +def test_ho_function(sct): code = """ +import numpy as np +np.array([]) + def hof(arg1): def inner(arg2): return arg1, arg2 - + return inner - + hof(1)(2) """ From 00e586881a74586294a1aa41d8c8a88d58bebc6f Mon Sep 17 00:00:00 2001 From: Jeroen Hermans Date: Sun, 5 Jan 2020 12:38:27 +0100 Subject: [PATCH 3/3] Remove v1 --- pythonwhat/checks/check_object.py | 8 +- pythonwhat/checks/has_funcs.py | 5 +- pythonwhat/probe.py | 254 -------- pythonwhat/sct_syntax.py | 39 -- pythonwhat/test_exercise.py | 17 +- pythonwhat/test_funcs/__init__.py | 27 - .../test_funcs/test_compound_statement.py | 603 ------------------ pythonwhat/test_funcs/test_function.py | 163 ----- pythonwhat/test_funcs/test_object.py | 62 -- pythonwhat/test_funcs/test_object_accessed.py | 59 -- pythonwhat/test_funcs/utils.py | 143 ----- pythonwhat/utils.py | 9 - tests/helper.py | 3 +- tests/test_author_warnings.py | 52 +- tests/test_check_function.py | 198 ------ tests/test_check_function_def.py | 22 - tests/test_check_if_else.py | 69 -- tests/test_check_list_comp.py | 171 +---- tests/test_check_logic.py | 121 ---- tests/test_check_object.py | 4 - tests/test_converters.py | 2 +- tests/test_docs.py | 6 +- tests/test_has_chosen.py | 5 - tests/test_has_expr.py | 21 +- tests/test_has_output.py | 14 - tests/test_messaging.py | 11 +- tests/test_spec.py | 12 +- tests/test_test_compound_statement.py | 117 ---- tests/test_test_exercise.py | 86 --- tests/test_test_object_accessed.py | 80 --- tests/test_test_with.py | 305 --------- tests/test_v2_only.py | 61 -- 32 files changed, 33 insertions(+), 2716 deletions(-) delete mode 100644 pythonwhat/probe.py delete mode 100644 pythonwhat/test_funcs/__init__.py delete mode 100644 pythonwhat/test_funcs/test_compound_statement.py delete mode 100644 pythonwhat/test_funcs/test_function.py delete mode 100644 pythonwhat/test_funcs/test_object.py delete mode 100644 pythonwhat/test_funcs/test_object_accessed.py delete mode 100644 pythonwhat/test_funcs/utils.py delete mode 100644 tests/test_test_compound_statement.py delete mode 100644 tests/test_test_exercise.py delete mode 100644 tests/test_test_object_accessed.py delete mode 100644 tests/test_test_with.py delete mode 100644 tests/test_v2_only.py diff --git a/pythonwhat/checks/check_object.py b/pythonwhat/checks/check_object.py index 09651e1a..c3fe499a 100644 --- a/pythonwhat/checks/check_object.py +++ b/pythonwhat/checks/check_object.py @@ -12,7 +12,6 @@ isDefinedCollInProcess, ) from pythonwhat.checks.check_funcs import part_to_child -from pythonwhat.utils import v2_only import pandas as pd import ast @@ -155,11 +154,8 @@ def __init__(self, n): and seeing if the result of running this expression in both student and solution process match. """ - - # Only do the assertion if PYTHONWHAT_V2_ONLY is set to '1' - if v2_only(): - extra_msg = "If you want to check the value of an object in e.g. a for loop, use `has_equal_value(name = 'my_obj')` instead." - state.assert_execution_root("check_object", extra_msg=extra_msg) + extra_msg = "If you want to check the value of an object in e.g. a for loop, use `has_equal_value(name = 'my_obj')` instead." + state.assert_execution_root("check_object", extra_msg=extra_msg) if missing_msg is None: missing_msg = "Did you define the {{typestr}} `{{index}}` without errors?" diff --git a/pythonwhat/checks/has_funcs.py b/pythonwhat/checks/has_funcs.py index 2ffff43a..ed12b0ad 100644 --- a/pythonwhat/checks/has_funcs.py +++ b/pythonwhat/checks/has_funcs.py @@ -152,9 +152,8 @@ def has_equal_ast(state, incorrect_msg=None, code=None, exact=True, append=None) Ex().check_function('numpy.mean').check_args('a').has_equal_ast() """ - if utils.v2_only(): - state.assert_is_not(["object_assignments"], "has_equal_ast", ["check_object"]) - state.assert_is_not(["function_calls"], "has_equal_ast", ["check_function"]) + state.assert_is_not(["object_assignments"], "has_equal_ast", ["check_object"]) + state.assert_is_not(["function_calls"], "has_equal_ast", ["check_function"]) if code and incorrect_msg is None: raise InstructorError.from_message( diff --git a/pythonwhat/probe.py b/pythonwhat/probe.py deleted file mode 100644 index 9a041e4d..00000000 --- a/pythonwhat/probe.py +++ /dev/null @@ -1,254 +0,0 @@ -import pprint as pp -import itertools -import inspect -from functools import partial -from collections import OrderedDict -from pythonwhat import test_funcs -from pythonwhat.State import State -from pythonwhat.checks.check_wrappers import state_partial - -TEST_NAMES = [ - "test_mc", - "test_or", - "test_with", - "test_import", - "test_object", - "test_correct", - "test_if_else", - "test_for_loop", - "test_function", - "test_list_comp", - "test_function_v2", - "test_data_frame", - "test_while_loop", - "test_student_typed", - "test_object_accessed", - "test_output_contains", - "test_expression_result", - "test_expression_output", - "test_function_definition", - "test_object_after_expression", -] - -SUB_TESTS = { - "test_if_else": ["test", "body", "orelse"], - "test_list_comp": ["comp_iter", "body", "ifs"], - "check_correct": ["check", "diagnose"], - "test_for_loop": ["for_iter", "body", "orelse"], - "test_while_loop": ["test", "body", "orelse"], - "test_with": ["context_tests", "body"], - "test_function_definition": ["body"], - "check_or": ["tests"], -} - - -class Tree(object): - def __init__(self): - """ - Represent a tree of nodes, by holding the currently active node. - - This class is necessary to put sub-tests onto a graph, because they may - exist inside of function calls. By getting the currently active node - from the tree, Probe instances may add subtests as children on that node, - and update it when recursing over sub-tests. - - """ - self.root = Node(name="root") - self.crnt_node = self.root - - @classmethod - def str_branch(cls, node, str_func=lambda s: str(s)): - # dict(getattr(s.data.get("bound_args", {}), 'arguments', {})) - f = node.data.get("func") - this_node = ( - " " * node.depth - + "(" - + getattr(f, "__name__", node.name) - + str_func(node) - + ")\n" - ) - return this_node + "".join( - map(lambda x: cls.str_branch(x, str_func), node.child_list) - ) - - def __str__(self): - return self.str_branch(self.crnt_node) - - def descend(self, node=None): - node = self.crnt_node if node is None else node - children = map(self.descend, node.child_list) - base = [node] if node.name != "root" else [] - return sum(children, base) - - def __iter__(self): - for ii in self.descend(self.crnt_node): - yield ii - - -class Node(object): - def __init__(self, child_list=None, data=None, name="unnamed", arg_name=""): - """ - Hold a function call with its bound arguments, along with child nodes. - - """ - self.parent = None - self.name = name - self.arg_name = arg_name - self.child_list = [] if child_list is None else child_list - self.data = {} if data is None else data - self.updated = False - # hacky way to add their argument name when a function of tests was given - - def __call__(self, state=None): - """Call original function with its arguments, and optional state""" - ba = self.data["bound_args"] - if state: - func = self.data["func"] - ba = inspect.signature(func).bind(state, *ba.args[1:], **ba.kwargs) - func(*ba.args, **ba.kwargs) - return state - else: - self.data["func"](*ba.args, **ba.kwargs) - ba.apply_defaults() - return ba.arguments["state"] - - def __str__(self): - return pp.pformat( - dict(getattr(self.data.get("bound_args", {}), "arguments", {})) - ) - - def __iter__(self): - for c in self.child_list: - yield c - - def partial(self): - """Return partial of original function call""" - ba = self.data["bound_args"] - return state_partial(self.data["func"], *ba.args[1:], **ba.kwargs) - - def update_child_calls(self): - """Replace child nodes on original function call with their partials""" - - for node in filter(lambda n: len(n.arg_name), self.child_list): - self.data["bound_args"].arguments[node.arg_name] = node.partial() - self.updated = True - - def remove_child(self, node): - index = self.child_list.index(node) - del self.child_list[index] - return index - - def add_child(self, child): - # since it is a tree, there is only one parent - # note this means we do not allow edges between same layer units - if child.parent: - child.parent.remove_child(child) - child.parent = self - self.child_list.append(child) - - def descend(self, include_me=True): - """Descend depth first into all child nodes""" - if include_me: - yield self - - for child in self.child_list: - yield child - yield from child.descend() - - @property - def depth(self): - if self.parent: - return self.parent.depth + 1 - else: - return 0 - - -class NodeList(Node): - def partial(self): - return [node.partial() for node in self.child_list] - - def update_child_calls(self): - pass - - -class Probe(object): - def __init__(self, tree, f, eval_on_call=False): - self.tree = tree - self.f = f - self.test_name = f.__name__ - self.eval_on_call = eval_on_call - # TODO: auto sub_test detection - self.sub_tests = SUB_TESTS.get(self.test_name) or [] - - def __call__(self, *args, **kwargs): - """Bind arguments to original function signature, and store in a Node - - This is used to discover what tests and sub-tests the SCT would like to - call, and defer them for later execution via their node instance. Node - instances are assembled into a tree. - - """ - if (len(args) > 0 and not isinstance(args[0], State)) or len(args) == 0: - # no state placeholder if a state is passed - args = ["state_placeholder"] + list(args) - - bound_args = inspect.signature(self.f).bind(*args, **kwargs) - - data = dict(bound_args=bound_args, func=self.f) - this_node = Node(data=data, name=self.test_name) - if self.tree is not None: - self.tree.crnt_node.add_child(this_node) - - # First pass to set up branches off node - arguments = bound_args.arguments - for subtest in self.sub_tests: # TODO: auto sub test detection - if subtest in arguments and arguments[subtest]: - self.build_sub_test_nodes( - arguments[subtest], self.tree, this_node, subtest - ) - - # Second pass to build node and all its children into a subtest - for node in this_node.descend(include_me=True): - if node.updated: # already built, e.g. node used multiple times - continue - else: - node.update_child_calls() - - if self.eval_on_call: - return this_node() - else: - return this_node - - @staticmethod - def build_sub_test_nodes(test, tree, node, arg_name): - # note that I've made the strong assumption that - # if not a function, then test is a dict, list or tuple of them - if isinstance(test, (list, tuple)): - nl = NodeList(name="List", arg_name=arg_name) - node.add_child(nl) - for ii, f in enumerate(test): - Probe.build_sub_test_nodes(f, tree, nl, str(ii)) - elif isinstance(test, Node): - # test was a lambdaless subtest call, which produced a node - # so need to tell it what its arg_name was on parent test - test.arg_name = arg_name - node.add_child(test) - elif callable(test): - # test was inside a lambda, function containing subtests or v2 F() chain object with subtests - # since either may contain multiple subtests, we put them in a node list - nl = NodeList(name="ListDeferred", arg_name=arg_name) - node.add_child(nl) - if tree is not None: - prev_node, tree.crnt_node = tree.crnt_node, nl - test() - tree.crnt_node = prev_node - else: - test() - elif test is not None: - raise Exception("Expected a function or list/tuple/dict of functions") - - -def build_probe_context(): - tree = Tree() - probe_context = {s: Probe(tree, getattr(test_funcs, s)) for s in TEST_NAMES} - return tree, probe_context diff --git a/pythonwhat/sct_syntax.py b/pythonwhat/sct_syntax.py index df846ee7..8e829547 100644 --- a/pythonwhat/sct_syntax.py +++ b/pythonwhat/sct_syntax.py @@ -1,32 +1,10 @@ from protowhat.sct_syntax import EagerChain, ExGen, LazyChainStart, state_dec_gen, LazyChain from pythonwhat.checks.check_wrappers import scts from pythonwhat.State import State -from pythonwhat.probe import Node, Probe, TEST_NAMES -from pythonwhat.utils import include_v1 -from pythonwhat import test_funcs -from functools import wraps # TODO: could define scts for check_wrappers at the module level sct_dict = scts.copy() - -def multi_dec(f): - """Decorator for multi to remove nodes for original test functions from root node""" - - @wraps(f) - def wrapper(*args, **kwargs): - args = ( - args[0] if len(args) == 1 and isinstance(args[0], (list, tuple)) else args - ) - for arg in args: - if isinstance(arg, Node) and arg.parent.name is "root": - arg.parent.remove_child(arg) - arg.update_child_calls() - return f(*args, **kwargs) - - return wrapper - - state_dec = state_dec_gen(sct_dict) # todo: __all__? @@ -49,22 +27,5 @@ def get_chains(): } -if include_v1(): - # Prepare SCTs that may be chained attributes ---------------------- - # decorate functions that may try to run test_* function nodes as subtests - # so they remove those nodes from the tree - for k in ["multi", "with_context"]: - sct_dict[k] = multi_dec(sct_dict[k]) - - # allow test_* functions as chained attributes - for k in TEST_NAMES: - sct_dict[k] = Probe(tree=None, f=getattr(test_funcs, k), eval_on_call=True) - - # original logical test_* functions behave like multi - # this is necessary to allow them to take check_* funcs as args - # since probe behavior will try to call all SCTs passed (assuming they're also probes) - for k in ["test_or", "test_correct"]: - sct_dict[k] = multi_dec(getattr(test_funcs, k)) - # Prepare check_funcs to be used alone (e.g. test = check_with().check_body()) v2_check_functions = {k: state_dec(v) for k, v in scts.items()} diff --git a/pythonwhat/test_exercise.py b/pythonwhat/test_exercise.py index 9bce76e2..f954e0af 100644 --- a/pythonwhat/test_exercise.py +++ b/pythonwhat/test_exercise.py @@ -4,7 +4,6 @@ from pythonwhat.utils import check_str, check_process from protowhat.Reporter import Reporter from protowhat.failure import Failure, InstructorError -from pythonwhat.utils import include_v1 def test_exercise( @@ -51,16 +50,11 @@ def test_exercise( ) State.root_state = state - tree, sct_cntxt = prep_context() + sct_cntxt = prep_context() # Actually execute SCTs exec(sct, sct_cntxt) - # Run remaining nodes on tree (v1 only) - if tree: - for test in tree.crnt_node: - test(state) - except Failure as e: if isinstance(e, InstructorError): # TODO: decide based on context @@ -88,7 +82,6 @@ def allow_errors(): def prep_context(): cntxt = {"success_msg": success_msg} from pythonwhat.sct_syntax import v2_check_functions - from pythonwhat.probe import build_probe_context imports = [ "from inspect import Parameter as param", @@ -98,17 +91,9 @@ def prep_context(): ] [exec(line, None, cntxt) for line in imports] - # only if PYTHONWHAT_V2_ONLY is not set, support v1 - if include_v1(): - tree, probe_cntxt = build_probe_context() - cntxt.update(probe_cntxt) - else: - tree = None - cntxt.update(v2_check_functions) # TODO: ChainStart instances cause errors when dill tries to pass manual converter functions # cntxt.update(get_chains()) - return tree, cntxt def setup_state(stu_code="", sol_code="", pec="", **kwargs): diff --git a/pythonwhat/test_funcs/__init__.py b/pythonwhat/test_funcs/__init__.py deleted file mode 100644 index 65acec61..00000000 --- a/pythonwhat/test_funcs/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -from .test_compound_statement import ( - test_with, - test_list_comp, - test_if_else, - test_for_loop, - test_while_loop, - test_expression_output, - test_expression_result, - test_object_after_expression, - test_function_definition, -) - -from .test_object import test_object, test_data_frame -from .test_function import test_function, test_function_v2 -from .test_object_accessed import test_object_accessed - -from pythonwhat.checks.check_logic import ( # noqa - check_or as test_or, - check_correct as test_correct, -) - -from pythonwhat.checks.has_funcs import ( # noqa - has_code as test_student_typed, - has_import as test_import, - has_output as test_output_contains, - has_chosen as test_mc, -) diff --git a/pythonwhat/test_funcs/test_compound_statement.py b/pythonwhat/test_funcs/test_compound_statement.py deleted file mode 100644 index c4572583..00000000 --- a/pythonwhat/test_funcs/test_compound_statement.py +++ /dev/null @@ -1,603 +0,0 @@ -from protowhat.utils_messaging import get_ord -from protowhat.sct_syntax import link_to_state -from pythonwhat.checks.check_funcs import ( - check_node, - check_part, - check_part_index, - with_context, -) -from pythonwhat.test_funcs.utils import fix_format, stringify, call -from pythonwhat.checks.check_logic import multi -from pythonwhat.checks.has_funcs import ( - has_equal_part_len, - has_equal_part, - has_equal_value, - has_equal_output, -) -from pythonwhat.checks.check_has_context import has_context -from functools import partial - -# this is done by the chain for v2 -# it's only needed when a new state is created and (possibly) used elsewhere -check_node = link_to_state(check_node) -check_part = link_to_state(check_part) -check_part_index = link_to_state(check_part_index) - - -def test_if_else(state, index=1, test=None, body=None, orelse=None): - """Test parts of the if statement. - - This test function will allow you to extract parts of a specific if statement and perform a set of tests - specifically on these parts. A for loop consists of three potential parts: the condition test, :code:`test`, - which specifies the condition of the if statement, the :code:`body`, which is what's executed if the condition is - True and a else part, :code:`orelse`, which will be executed if the condition is not True.:: - - if 5 == 3: - print("success") - else: - print("fail") - - Has :code:`5 == 3` as the condition test, :code:`print("success")` as the body and :code:`print("fail")` as the else part. - - Args: - index (int): index of the function call to be checked. Defaults to 1. - test: this argument holds the part of code that will be ran to check the condition test of the if statement. - It should be passed as a lambda expression or a function definition. The functions that are ran should - be other pythonwhat test functions, and they will be tested specifically on only the condition test of - the if statement. - body: this argument holds the part of code that will be ran to check the body of the if statement. - It should be passed as a lambda expression or a function definition. The functions that are ran should - be other pythonwhat test functions, and they will be tested specifically on only the body of - the if statement. - orelse: this argument holds the part of code that will be ran to check the else part of the if statement. - It should be passed as a lambda expression or a function definition. The functions that are ran should - be other pythonwhat test functions, and they will be tested specifically on only the else part of - the if statement. - - :Example: - - Student code:: - - a = 12 - if a > 3: - print('test %d' % a) - - Solution code:: - - a = 4 - if a > 3: - print('test %d' % a) - - SCT:: - - test_if_else(1, - body = test_expression_output( - extra_env = { 'a': 5 } - incorrect_msg = "Print out the correct things")) - - This SCT will pass as :code:`test_expression_output()` is ran on the body of the if statement and it will output - the same thing in the solution as in the student code. - """ - state = check_node( - state, "if_elses", index - 1, typestr="{{ordinal}} if expression" - ) - multi(check_part(state, "test", "condition"), test) - multi(check_part(state, "body", "body"), body) - multi(check_part(state, "orelse", "else part"), orelse) - - -def test_for_loop(state, index=1, for_iter=None, body=None, orelse=None): - """Test parts of the for loop. - - This test function will allow you to extract parts of a specific for loop and perform a set of tests - specifically on these parts. A for loop consists of two parts: the sequence, `for_iter`, which is the - values over which are looped, and the `body`. A for loop can have a else part as well, `orelse`, but - this is almost never used.:: - - for i in range(10): - print(i) - - Has :code:`range(10)` as the sequence and :code:`print(i)` as the body. - - Args: - index (int): index of the function call to be checked. Defaults to 1. - for_iter: this argument holds the part of code that will be ran to check the sequence of the for loop. - It should be passed as a lambda expression or a function. The functions that are ran should - be other pythonwhat test functions, and they will be tested specifically on only the sequence part of - the for loop. - body: this argument holds the part of code that will be ran to check the body of the for loop. - It should be passed as a lambda expression or a function. The functions that are ran should - be other pythonwhat test functions, and they will be tested specifically on only the body of - the for loop. - orelse: this argument holds the part of code that will be ran to check the else part of the for loop. - It should be passed as a lambda expression or a function. The functions that are ran should - be other pythonwhat test functions, and they will be tested specifically on only the else part of - the for loop. - - :Example: - Student code:: - - for i in range(10): - print(i) - - Solution code:: - - for n in range(10): - print(n) - - SCT:: - - test_for_loop(1, - for_iter = test_function("range"), - body = test_expression_output(context_val = [5]) - - This SCT will evaluate to True as the function :code:`range` is used in the sequence and the function - :code:`test_exression_output()` will pass on the body code. - """ - state = check_node(state, "for_loops", index - 1, "{{ordinal}} for loop") - - multi(check_part(state, "iter", "sequence part"), for_iter) - multi(check_part(state, "body", "body"), body) - multi(check_part(state, "orelse", "else part"), orelse) - - -def test_while_loop(state, index=1, test=None, body=None, orelse=None): - """Test parts of the while loop. - - This test function will allow you to extract parts of a specific while loop and perform a set of tests - specifically on these parts. A while loop generally consists of two parts: the condition test, :code:`test`, - which is the condition that is tested each loop, and the :code:`body`. A for while can have a else part as well, - :code:`orelse`, but this is almost never used.:: - - a = 10 - while a < 5: - print(a) - a -= 1 - - Has :code:`a < 5` as the condition test and `print(i)` as the body. - - Args: - index (int): index of the function call to be checked. Defaults to 1. - test: this argument holds the part of code that will be ran to check the condition test of the while loop. - It should be passed as a lambda expression or a function definition. The functions that are ran should - be other pythonwhat test functions, and they will be tested specifically on only the condition test of - the while loop. - body: this argument holds the part of code that will be ran to check the body of the while loop. - It should be passed as a lambda expression or a function definition. The functions that are ran should - be other pythonwhat test functions, and they will be tested specifically on only the body of - the while loop. - orelse: this argument holds the part of code that will be ran to check the else part of the while loop. - It should be passed as a lambda expression or a function definition. The functions that are ran should - be other pythonwhat test functions, and they will be tested specifically on only the else part of - the while loop. - - :Example: - - Student code:: - - a = 10 - while a < 5: - print(a) - a -= 1 - - Solution code:: - - a = 20 - while a < 5: - print(a) - a -= 1 - - SCT:: - - test_while_loop(1, - test = test_expression_result({"a": 5}), - body = test_expression_output({"a": 5})) - - This SCT will evaluate to True as condition test will have thes same result in student - and solution code and `test_exression_output()` will pass on the body code. - """ - state = check_node(state, "whiles", index - 1, "{{ordinal}} while loop") - multi(check_part(state, "test", "condition"), test) - multi(check_part(state, "body", "body"), body) - multi(check_part(state, "orelse", "else part"), orelse) - - -def test_function_definition( - state, - name, - arg_names=True, - arg_defaults=True, - body=None, - results=None, - outputs=None, - errors=None, - not_called_msg=None, - nb_args_msg=None, - other_args_msg=None, - arg_names_msg=None, - arg_defaults_msg=None, - wrong_result_msg=None, - wrong_output_msg=None, - no_error_msg=None, - wrong_error_msg=None, -): - """Test a function definition. - - This function helps you test a function definition. Generally four things can be tested: - 1) The argument names of the function (including if the correct defaults are used) - 2) The body of the functions (does it output correctly, are the correct functions used) - 3) The return value with a certain input - 4) The output value with a certain input - 5) Whether certain inputs generate an error and what type of error - - Custom feedback messages can be set for all these parts, default messages are generated - automatically if none are set. - - Args: - name (str): the name of the function definition to be tested. - arg_names (bool): if True, the argument names will be tested, if False they won't be tested. Defaults - to True. - arg_defaults (bool): if True, the default values of the arguments will be tested, if False they won't - be tested. Defaults to True. - body: this arguments holds the part of the code that will be ran to check the body of the function - definition. It should be passed as a lambda expression or a function. The functions that are - ran should be other pythonwhat test functions, and they will be tested specifically on only the - body of the for loop. Defaults to None. - results (list(list)): a list of lists representing arguments that should be passed to the defined - function. These arguments are passed to the function in the student environment and the solution - environment, the results (what's returned) are compared. - outputs (list(list)): a list of lists representing arguments that should be passed to the defined - function. These arguments are passed to the function in the student environment and the solution - environment, the outpus are compared. - errors (list(list)): a list of lists representing arguments that should be passed to the defined - function. These arguments are passed to the function in the student environment and the solution - environment, the errors they generate are compared. - not_called_msg (str): message if the function is not defined. - nb_args_msg (str): message if the number of arguments do not matched. - arg_names_msg (str): message if the argument names do not match. - arg_defaults_msg (str): message if the argument default values do not match. - wrong_result_msg (str): message if one of the tested function calls' result did not match. - wrong_output_msg (str): message if one of the tested functions calls' output did not match. - no_error_msg (str): message if one of the tested function calls' result did not generate an error. - wrong_error_msg (str): message if the error that one of the tested function calls generated did not match. - - :Example: - - Student code:: - - def shout( word, times = 3): - shout_word = not_word + '???' - print( shout_word ) - return word * times - - Solution code:: - - def shout( word = 'help', times = 3 ): - shout_word = word + '!!!' - print( shout_word ) - return word * times - - SCT:: - - test_function_definition('shout') # fail - test_function_definition('shout', arg_defaults = False) # pass - test_function_definition('shout', arg_defaults = False, # fail - outputs = [('help')]) - - test_function_definition('shout', arg_defaults = False, # pass - results = [('help', 2)]) - - test_function_definition('shout', args_defaults = False # pass - body = test_function('print', args = []])) - """ - - # what the function will be referred to as - child = check_node(state, "function_defs", name, "definition of `{{index}}()`") - - test_args( - child, arg_names, arg_defaults, nb_args_msg, arg_names_msg, arg_defaults_msg - ) - - multi(check_part(child, "body", ""), body) - - # Test function calls ----------------------------------------------------- - - for el in results or []: - el = fix_format(el) - call( - child, - el, - "value", - incorrect_msg=wrong_result_msg, - error_msg=wrong_result_msg, - argstr="`{}{}`".format(name, stringify(el)), - ) - - for el in outputs or []: - el = fix_format(el) - call( - child, - el, - "output", - incorrect_msg=wrong_output_msg, - error_msg=wrong_output_msg, - argstr="`{}{}`".format(name, stringify(el)), - ) - - for el in errors or []: - el = fix_format(el) - call( - child, - el, - "error", - incorrect_msg=wrong_error_msg, - error_msg=no_error_msg, - argstr="`{}{}`".format(name, stringify(el)), - ) - - -def test_args( - state, arg_names, arg_defaults, nb_args_msg, arg_names_msg, arg_defaults_msg -): - - MSG_NUM_ARGS = "You should define {{parent[typestr]}} with {{sol_len}} arguments, instead got {{stu_len}}." - MSG_BAD_ARG_NAME = "The {{parent[ordinal]}} {{parent[part]}} should be called `{{sol_part[name]}}`, instead got `{{stu_part[name]}}`." - MSG_BAD_DEFAULT = ( - "The {{parent[part]}} `{{stu_part[name]}}` should have no default." - ) - MSG_INC_DEFAULT = ( - "The {{parent[part]}} `{{stu_part[name]}}` does not have the correct default." - ) - - MSG_NO_VARARG = "Have you specified an argument to take a `*` argument and named it `{{sol_part['*args'][name]}}`?" - MSG_NO_KWARGS = "Have you specified an argument to take a `**` argument and named it `{{sol_part['**kwargs'][name]}}`?" - MSG_VARARG_NAME = "Have you specified an argument to take a `*` argument and named it `{{sol_part[name]}}`?" - MSG_KWARG_NAME = "Have you specified an argument to take a `**` argument and named it `{{sol_part[name]}}`?" - - if arg_names or arg_defaults: - # test number of args - has_equal_part_len(state, "_spec1_args", nb_args_msg or MSG_NUM_ARGS) - - # iterate over each arg, testing name and default - for ii in range(len(state.solution_parts["_spec1_args"])): - # get argument state - arg_state = check_part_index( - state, "_spec1_args", ii, "argument", "NO MISSING MSG" - ) - # test exact name - has_equal_part(arg_state, "name", arg_names_msg or MSG_BAD_ARG_NAME) - - if arg_defaults: - # test whether is default - has_equal_part( - arg_state, "is_default", arg_defaults_msg or MSG_BAD_DEFAULT - ) - # test default value, use if to prevent running a process no default - if arg_state.solution_parts["is_default"]: - has_equal_value( - arg_state, - incorrect_msg=arg_defaults_msg or MSG_INC_DEFAULT, - append=True, - ) - - # test *args and **kwargs - if state.solution_parts["*args"]: - vararg = check_part(state, "*args", "", missing_msg=MSG_NO_VARARG) - has_equal_part(vararg, "name", MSG_VARARG_NAME) - - if state.solution_parts["**kwargs"]: - kwarg = check_part(state, "**kwargs", "", missing_msg=MSG_NO_KWARGS) - has_equal_part(kwarg, "name", MSG_KWARG_NAME) - - -def test_expression_result( - state, - extra_env=None, - context_vals=None, - incorrect_msg=None, - expr_code=None, - pre_code=None, - error_msg=None, - **kwargs -): - has_equal_value( - state, - incorrect_msg=incorrect_msg, - error_msg=error_msg, - extra_env=extra_env, - context_vals=context_vals, - expr_code=expr_code, - pre_code=pre_code, - **kwargs - ) - - -def test_expression_output( - state, - extra_env=None, - context_vals=None, - incorrect_msg=None, - eq_condition="equal", - expr_code=None, - pre_code=None, - **kwargs -): - has_equal_output( - state, - incorrect_msg=incorrect_msg, - extra_env=extra_env, - context_vals=context_vals, - expr_code=expr_code, - pre_code=pre_code, - **kwargs - ) - - -def test_object_after_expression( - state, - name, - extra_env=None, - context_vals=None, - undefined_msg=None, - incorrect_msg=None, - expr_code=None, - pre_code=None, - **kwargs -): - state.highlight = ( - state.ast_dispatcher.find("object_assignments", state.student_ast) - .get(name, {}) - .get("highlight") - ) - has_equal_value( - state, - incorrect_msg=incorrect_msg, - error_msg=undefined_msg, - undefined_msg=undefined_msg, - extra_env=extra_env, - context_vals=context_vals, - pre_code=pre_code, - name=name, - expr_code=expr_code, - **kwargs - ) - - -def test_with( - state, - index, - context_vals=False, # whether to check number of context vals - context_tests=None, # check on context expressions - body=None, - undefined_msg=None, - context_vals_len_msg=None, - context_vals_msg=None, -): - """Test a with statement. -with open_file('...') as bla: - - [ open_file('...').__enter__() ] - - -with open_file('...') as file: - [ ] - - """ - - MSG_NUM_CTXT = "Make sure to use the correct number of context variables. It seems you defined too many." - MSG_NUM_CTXT2 = "Make sure to use the correct number of context variables. It seems you defined too little." - MSG_CTXT_NAMES = "Make sure to use the correct context variable names. Was expecting `{{sol_vars}}` but got `{{stu_vars}}`." - - check_with = partial( - check_node, state, "withs", index - 1, "{{ordinal}} `with` statement" - ) - - child = check_with() - child2 = check_with() - - if context_vals: - # test context var names ---- - has_context( - child, incorrect_msg=context_vals_msg or MSG_CTXT_NAMES, exact_names=True - ) - - # test num context vars ---- - has_equal_part_len(child, "context", MSG_NUM_CTXT) - - # Context sub tests ---- - if context_tests and not isinstance(context_tests, list): - context_tests = [context_tests] - - for i, context_test in enumerate(context_tests or []): - # partial the substate check, because the function uses two prepended messages - def check_context(state): - return check_part_index( - state, - "context", - i, - "%s context" % get_ord(i + 1), - missing_msg=MSG_NUM_CTXT2, - ) - - check_context(child) # test exist - - ctxt_state = check_context(child2) # sub tests - multi(ctxt_state, context_test) - - # Body sub tests ---- - if body is not None: - body_state = check_part(child2, "body", "body") - - with_context(body_state, body, child=child) - - -def test_list_comp( - state, - index=1, - not_called_msg=None, - comp_iter=None, - iter_vars_names=False, - incorrect_iter_vars_msg=None, - body=None, - ifs=None, - insufficient_ifs_msg=None, -): - """Test list comprehension.""" - kwargs = locals().copy() - kwargs["typestr"] = "{{ordinal}} list comprehension" - kwargs["comptype"] = "list_comps" - test_comp(**kwargs) - - -def test_comp( - state, - typestr, - comptype, - index, - iter_vars_names, - not_called_msg, - insufficient_ifs_msg, - incorrect_iter_vars_msg, - comp_iter, - ifs, - key=None, - body=None, - value=None, - rep=None, -): - - MSG_INCORRECT_ITER_VARS = "Have you used the correct iterator variables?" - MSG_INCORRECT_NUM_ITER_VARS = "Have you used {{num_vars}} iterator variables?" - MSG_INSUFFICIENT_IFS = "Have you used {{sol_len}} ifs?" - - # make sure other messages are set to default if None - if insufficient_ifs_msg is None: - insufficient_ifs_msg = MSG_INSUFFICIENT_IFS - - # get comprehension - child = check_node(state, comptype, index - 1, typestr, missing_msg=not_called_msg) - - # test comprehension iter and its variable names (or number of variables) - if comp_iter: - multi(check_part(child, "iter", "iterable part"), comp_iter) - - # test iterator variables - default_msg = ( - MSG_INCORRECT_ITER_VARS if iter_vars_names else MSG_INCORRECT_NUM_ITER_VARS - ) - has_context(child, incorrect_iter_vars_msg or default_msg, iter_vars_names) - - # test the main expressions. - if body: - multi(check_part(child, "body", "body"), body) # list and gen comp - if key: - multi(check_part(child, "key", "key part"), key) # dict comp - if value: - multi(check_part(child, "value", "value part"), value) # "" - - # test a list of ifs. each entry corresponds to a filter in the comprehension. - for i, if_test in enumerate(ifs or []): - # test that ifs are same length - has_equal_part_len(child, "ifs", insufficient_ifs_msg) - # test individual ifs - multi(check_part_index(child, "ifs", i, get_ord(i + 1) + " if"), if_test) diff --git a/pythonwhat/test_funcs/test_function.py b/pythonwhat/test_funcs/test_function.py deleted file mode 100644 index 22175a55..00000000 --- a/pythonwhat/test_funcs/test_function.py +++ /dev/null @@ -1,163 +0,0 @@ -from functools import partial - -from protowhat.sct_syntax import link_to_state -from pythonwhat.checks.check_function import check_function -from protowhat.failure import TestFail, InstructorError -from pythonwhat.checks.check_funcs import check_args -from pythonwhat.checks.has_funcs import has_equal_value, has_equal_ast, has_printout - -# this is done by the chain for v2 -# it's only needed when a new state is created and (possibly) used elsewhere -check_function = link_to_state(check_function) - - -def arg_test(state, name, do_eval, missing_msg, incorrect_msg): - arg_state = check_args(state, name=name, missing_msg=missing_msg) - - append = incorrect_msg is None - - if isinstance(do_eval, bool): - if do_eval: - has_equal_value( - arg_state, incorrect_msg=incorrect_msg, append=append, copy=False - ) - else: - has_equal_ast(arg_state, incorrect_msg=incorrect_msg, append=append) - - -def test_function( - state, - name, - index=1, - args=None, - keywords=None, - eq_condition="equal", - do_eval=True, - not_called_msg=None, - args_not_specified_msg=None, - incorrect_msg=None, - add_more=False, - **kwargs -): - index = index - 1 - - # if root-level (not in compound statement) calls: use has_printout - if name == "print" and state.parent_state is None and do_eval: - try: - return has_printout(state, index=index, not_printed_msg=incorrect_msg) - except TestFail: - # The test didn't pass; just continue with the more strict check_function test. - pass - - fun_state = check_function( - state, name=name, index=index, missing_msg=not_called_msg, signature=False - ) - - if args is None: - args = [ - k - for k, value in fun_state.solution_parts["args"].items() - if isinstance(k, int) - ] - - if keywords is None: - keywords = [ - k - for k, value in fun_state.solution_parts["args"].items() - if isinstance(k, str) - ] - - arg_test_partial = partial( - arg_test, - fun_state, - do_eval=do_eval, - missing_msg=args_not_specified_msg, - incorrect_msg=incorrect_msg, - ) - - [arg_test_partial(name=i) for i in range(len(args))] - [arg_test_partial(name=keyword) for keyword in keywords] - - return state - - -def test_function_v2( - state, - name, - index=1, - params=[], - signature=True, - eq_condition="equal", - do_eval=True, - not_called_msg=None, - params_not_matched_msg=None, - params_not_specified_msg=None, - incorrect_msg=None, - add_more=False, - **kwargs -): - - index = index - 1 - - if not isinstance(params, list): - raise InstructorError.from_message( - "Inside test_function_v2, make sure to specify a LIST of params." - ) - - if isinstance(do_eval, bool) or do_eval is None: - do_eval = [do_eval] * len(params) - - if len(params) != len(do_eval): - raise InstructorError.from_message( - "Inside test_function_v2, make sure that do_eval has the same length as params." - ) - - # if params_not_specified_msg is a str or None, convert into list - if isinstance(params_not_specified_msg, str) or params_not_specified_msg is None: - params_not_specified_msg = [params_not_specified_msg] * len(params) - - if len(params) != len(params_not_specified_msg): - raise InstructorError.from_message( - "Inside test_function_v2, make sure that params_not_specified_msg has the same length as params." - ) - - # if incorrect_msg is a str or None, convert into list - if isinstance(incorrect_msg, str) or incorrect_msg is None: - incorrect_msg = [incorrect_msg] * len(params) - - if len(params) != len(incorrect_msg): - raise InstructorError.from_message( - "Inside test_function_v2, make sure that incorrect_msg has the same length as params." - ) - - # if root-level (not in compound statement) calls that can be evaluated: use has_printout - eligible = do_eval[0] if isinstance(do_eval, list) and len(do_eval) > 0 else do_eval - if name == "print" and state.parent_state is None and eligible: - try: - return has_printout(state, index=index, not_printed_msg=incorrect_msg[0]) - except TestFail: - # The test didn't pass; just continue with the more strict check_function test. - pass - - if len(params) == 0: - signature = False - - fun_state = check_function( - state, - name=name, - index=index, - missing_msg=not_called_msg, - params_not_matched_msg=params_not_matched_msg, - signature=signature, - ) - - for i in range(len(params)): - arg_test( - fun_state, - name=params[i], - do_eval=do_eval[i], - missing_msg=params_not_specified_msg[i], - incorrect_msg=incorrect_msg[i], - ) - - return state diff --git a/pythonwhat/test_funcs/test_object.py b/pythonwhat/test_funcs/test_object.py deleted file mode 100644 index c0b441d5..00000000 --- a/pythonwhat/test_funcs/test_object.py +++ /dev/null @@ -1,62 +0,0 @@ -from protowhat.sct_syntax import link_to_state -from pythonwhat.tasks import getColumnsInProcess -from pythonwhat.checks.check_object import check_object, check_df, check_keys -from pythonwhat.checks.has_funcs import has_equal_value - -# this is done by the chain for v2 -# it's only needed when a new state is created and (possibly) used elsewhere -check_object = link_to_state(check_object) -check_df = link_to_state(check_df) -check_keys = link_to_state(check_keys) - - -def test_object( - state, - name, - eq_condition="equal", - eq_fun=None, - do_eval=True, - undefined_msg=None, - incorrect_msg=None, -): - - expand_msg = "" if undefined_msg or incorrect_msg else None - child = check_object(state, name, undefined_msg, expand_msg=expand_msg) - - if do_eval: - has_equal_value(child, incorrect_msg) - - -def test_data_frame( - state, - name, - columns=None, - undefined_msg=None, - not_data_frame_msg=None, - undefined_cols_msg=None, - incorrect_msg=None, -): - """Test a pandas dataframe. - """ - - expand_msg = ( - "" - if undefined_msg or not_data_frame_msg or undefined_cols_msg or incorrect_msg - else None - ) - - child = check_df( - state, - name, - undefined_msg, - not_instance_msg=not_data_frame_msg, - expand_msg=expand_msg, - ) - - # if columns not set, figure them out from solution - if columns is None: - columns = getColumnsInProcess(name, child.solution_process) - - for col in columns: - colstate = check_keys(child, col, missing_msg=undefined_cols_msg) - has_equal_value(colstate, incorrect_msg=incorrect_msg) diff --git a/pythonwhat/test_funcs/test_object_accessed.py b/pythonwhat/test_funcs/test_object_accessed.py deleted file mode 100644 index 6e01ae99..00000000 --- a/pythonwhat/test_funcs/test_object_accessed.py +++ /dev/null @@ -1,59 +0,0 @@ -from protowhat.Feedback import FeedbackComponent -from protowhat.utils_messaging import get_times -from pythonwhat.Test import BiggerTest - - -def test_object_accessed(state, name, times=1, not_accessed_msg=None): - """Test if object accessed - - Checks whether an object, or the attribute of an object, are accessed - - Args: - name (str): the name of the object that should be accessed; can contain dots (for attributes) - times (int): how often the object specified in name should be accessed. - not_accessed_msg (str): custom feedback message when the object was not accessed. - - Examples: - - - Student code - - | ``import numpy as np`` - | ``arr = np.array([1, 2, 3])`` - | ``x = arr.shape`` - - Solution code - - | ``import numpy as np`` - | ``arr = np.array([1, 2, 3])`` - | ``x = arr.shape`` - | ``t = arr.dtype`` - - SCT - - | ``test_object_accessed("arr")``: pass. - | ``test_object_accessed("arr.shape")``: pass. - | ``test_object_accessed("arr.dtype")``: fail. - """ - student_object_accesses = state.ast_dispatcher.find( - "object_accesses", state.student_ast - ) - student_mappings = state.ast_dispatcher.find("oa_mappings", state.student_ast) - - if not not_accessed_msg: - stud_name = name - if "." in stud_name: - for orig, full_name in student_mappings.items(): - if name.startswith(full_name): - stud_name = name.replace(full_name, orig) - - add = " at least %s" % get_times(times) if times > 1 else "" - not_accessed_msg = "Have you accessed `%s`%s?" % (stud_name, add) - - # name should be contained inside the student_object_accesses. - # hack: add a dot and do a match on the name with the dot, - # to make sure you're not matching substrings - student_hits = [c for c in student_object_accesses if name + "." in c + "."] - state.do_test( - BiggerTest(len(student_hits) + 1, times, FeedbackComponent(not_accessed_msg)) - ) diff --git a/pythonwhat/test_funcs/utils.py b/pythonwhat/test_funcs/utils.py deleted file mode 100644 index b23036c2..00000000 --- a/pythonwhat/test_funcs/utils.py +++ /dev/null @@ -1,143 +0,0 @@ -import ast - -from protowhat.Feedback import FeedbackComponent -from protowhat.failure import InstructorError, debugger -from pythonwhat.Test import EqualTest -from pythonwhat.checks.has_funcs import evalCalls -from pythonwhat.tasks import ReprFail - - -def fix_format(arguments): - if isinstance(arguments, str): - arguments = (arguments,) - if isinstance(arguments, tuple): - arguments = list(arguments) - - if isinstance(arguments, list): - arguments = {"args": arguments, "kwargs": {}} - - if ( - not isinstance(arguments, dict) - or "args" not in arguments - or "kwargs" not in arguments - ): - raise ValueError( - "Wrong format of arguments in 'results', 'outputs' or 'errors'; either a list, or a dictionary with names args (a list) and kwargs (a dict)" - ) - - return arguments - - -def stringify(arguments): - vararg = str(arguments["args"])[1:-1] - kwarg = ", ".join( - ["%s = %s" % (key, value) for key, value in arguments["kwargs"].items()] - ) - if len(vararg) == 0: - if len(kwarg) == 0: - return "()" - else: - return "(" + kwarg + ")" - else: - if len(kwarg) == 0: - return "(" + vararg + ")" - else: - return "(" + ", ".join([vararg, kwarg]) + ")" - - -# TODO: test string syntax with check_function_def -# test argument syntax with check_lambda_function -def run_call(args, node, process, get_func, **kwargs): - # Get function expression - if isinstance(node, ast.FunctionDef): # function name - func_expr = ast.Name(id=node.name, ctx=ast.Load()) - elif isinstance(node, ast.Lambda): # lambda body expr - func_expr = node - else: - raise InstructorError.from_message( - "Only function definition or lambda may be called" - ) - - ast.fix_missing_locations(func_expr) - return get_func(process=process, tree=func_expr, call=args, **kwargs) - - -MSG_CALL_INCORRECT = "Calling {{argstr}} should {{action}} `{{str_sol}}`, instead got {{str_stu if str_stu == 'no printouts' else '`' + str_stu + '`'}}." -MSG_CALL_ERROR = "Calling {{argstr}} should {{action}} `{{str_sol}}`, instead it errored out: `{{str_stu}}`." -MSG_CALL_ERROR_INV = ( - "Calling {{argstr}} should {{action}} `{{str_sol}}`, instead got `{{str_stu}}`." -) - - -def call( - state, - args, - test="value", - incorrect_msg=None, - error_msg=None, - argstr=None, - func=None, - **kwargs -): - """Use ``check_call()`` in combination with ``has_equal_x()`` instead. - """ - - if incorrect_msg is None: - incorrect_msg = MSG_CALL_INCORRECT - if error_msg is None: - error_msg = MSG_CALL_ERROR_INV if test == "error" else MSG_CALL_ERROR - - assert test in ("value", "output", "error") - - get_func = evalCalls[test] - - # Run for Solution -------------------------------------------------------- - eval_sol, str_sol = run_call( - args, state.solution_parts["node"], state.solution_process, get_func, **kwargs - ) - - if (test == "error") ^ isinstance(eval_sol, Exception): - with debugger(state): - state.report( - "Calling {{argstr}} resulted in an error (or not an error if testing for one). Error message: {{type_err}} {{str_sol}}", - dict(type_err=type(eval_sol), str_sol=str_sol, argstr=argstr), - ) - - if isinstance(eval_sol, ReprFail): - with debugger(state): - state.report( - "Can't get the result of calling {{argstr}}: {{eval_sol.info}}", - dict(argstr=argstr, eval_sol=eval_sol), - ) - - # Run for Submission ------------------------------------------------------ - eval_stu, str_stu = run_call( - args, state.student_parts["node"], state.student_process, get_func, **kwargs - ) - action_strs = { - "value": "return", - "output": "print out", - "error": "error out with the message", - } - fmt_kwargs = { - "part": argstr, - "argstr": argstr, - "str_sol": str_sol, - "str_stu": str_stu, - "action": action_strs[test], - } - - # either error test and no error, or vice-versa - stu_node = state.student_parts["node"] - stu_state = state.to_child(highlight=stu_node) - if (test == "error") ^ isinstance(eval_stu, Exception): - stu_state.report(error_msg, fmt_kwargs) - - # incorrect result - stu_state.do_test( - EqualTest( - eval_sol, eval_stu, FeedbackComponent(incorrect_msg, fmt_kwargs), func - ) - ) - - return state diff --git a/pythonwhat/utils.py b/pythonwhat/utils.py index 7a36f7de..29b4ff35 100644 --- a/pythonwhat/utils.py +++ b/pythonwhat/utils.py @@ -1,14 +1,5 @@ from types import ModuleType import copy -import os - - -def include_v1(): - return os.environ.get("PYTHONWHAT_V2_ONLY", "") != "1" - - -def v2_only(): - return not include_v1() def shorten_str(text, to_chars=100): diff --git a/tests/helper.py b/tests/helper.py index 9e32b672..c51901cb 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -147,8 +147,7 @@ def replace_test_if(sct): @contextmanager -def set_v2_only_env(new): - key = "PYTHONWHAT_V2_ONLY" +def set_env(key, new): old = os.environ.get(key) try: os.environ[key] = new diff --git a/tests/test_author_warnings.py b/tests/test_author_warnings.py index 0ad735dc..7d9a96d6 100644 --- a/tests/test_author_warnings.py +++ b/tests/test_author_warnings.py @@ -16,7 +16,7 @@ def test_converter_err(): data = { "DC_CODE": code, "DC_SOLUTION": code, - "DC_SCT": """def convert(): return abc\nset_converter('numpy.ndarray', convert); test_object('x') """, + "DC_SCT": """def convert(): return abc\nset_converter('numpy.ndarray', convert); check_object('x') """, } with pytest.raises(InstructorError): helper.run(data) @@ -187,21 +187,13 @@ def test_check_object_on_root(): def test_check_object_not_on_root(): code = "for i in range(3): x = 1" s = setup_state(code, code) - with helper.set_v2_only_env(""): + with pytest.raises( + InstructorError, + match=r"`check_object\(\)` should only be called focusing on a full script, following `Ex\(\)` or `run\(\)`\. If you want to check the value of an object in e.g. a for loop, use `has_equal_value\(name = 'my_obj'\)` instead.", + ): s.check_for_loop().check_body().check_object("x") -def test_check_object_not_on_root_v2(): - code = "for i in range(3): x = 1" - s = setup_state(code, code) - with helper.set_v2_only_env("1"): - with pytest.raises( - InstructorError, - match=r"`check_object\(\)` should only be called focusing on a full script, following `Ex\(\)` or `run\(\)`\. If you want to check the value of an object in e.g. a for loop, use `has_equal_value\(name = 'my_obj'\)` instead.", - ): - s.check_for_loop().check_body().check_object("x") - - def test_is_instance_not_on_check_object(): code = "round(3)" s = setup_state(code, code) @@ -225,35 +217,21 @@ def test_check_keys_not_on_check_object(): def test_has_equal_ast_on_check_object(): code = "x = 1" s = setup_state(code, code) - s.check_object("x").has_equal_ast() - - -def test_has_equal_ast_on_check_object_v2(): - code = "x = 1" - s = setup_state(code, code) - with helper.set_v2_only_env("1"): - with pytest.raises( - InstructorError, - match=r"`has_equal_ast\(\)` should not be called on `check_object\(\)`\.", - ): - s.check_object("x").has_equal_ast() + with pytest.raises( + InstructorError, + match=r"`has_equal_ast\(\)` should not be called on `check_object\(\)`\.", + ): + s.check_object("x").has_equal_ast() def test_has_equal_ast_on_check_function(): code = "round(1)" s = setup_state(code, code) - s.check_function("round").has_equal_ast() - - -def test_has_equal_ast_on_check_function_v2(): - code = "round(1)" - s = setup_state(code, code) - with helper.set_v2_only_env("1"): - with pytest.raises( - InstructorError, - match=r"`has_equal_ast\(\)` should not be called on `check_function\(\)`\.", - ): - s.check_function("round").has_equal_ast() + with pytest.raises( + InstructorError, + match=r"`has_equal_ast\(\)` should not be called on `check_function\(\)`\.", + ): + s.check_function("round").has_equal_ast() def test_check_call_not_on_check_function_def(): diff --git a/tests/test_check_function.py b/tests/test_check_function.py index 85636fc5..2c69c56b 100644 --- a/tests/test_check_function.py +++ b/tests/test_check_function.py @@ -286,8 +286,6 @@ def test_check_function_parser_mappings_2(): # Incorrect usage ------------------------------------------------------------- - - @pytest.mark.parametrize( "sct", [ @@ -305,202 +303,6 @@ def test_check_function_weirdness(sct, sol): helper.run(data) -# Old implementation: test_function ------------------------------------------- -# NOTE: These tests shows how it _currently_ works, -# but test_function can be improved! - - -@pytest.mark.parametrize( - "stu, passes", - [ - ("", False), - ("my_fun(2, 2)", False), - ("my_fun(2, b=2)", False), - ("my_fun(a=2, b=2)", False), - ("my_fun(1, 3)", False), - ("my_fun(1, b=3)", False), - ("my_fun(a=1, b=3)", False), - ("my_fun(1, 2)", False), # this failure is THE limitation of test_function! - ("my_fun(1, b=2)", False), # this failure is THE limitation of test_function! - ("my_fun(a=1, b=2)", True), - ], -) -def test_test_function_basic(stu, passes): - s = setup_state(stu, "my_fun(a = 1, b = 2)", pec="def my_fun(a, b): pass") - with helper.verify_sct(passes): - s.test_function("my_fun") - - -@pytest.mark.parametrize( - "stu, passes", - [ - ("", False), - ("my_fun(2, 2)", False), - ("my_fun(2, b=2)", False), - ("my_fun(a=2, b=2)", False), - ("my_fun(1, 3)", True), - ("my_fun(1, b=3)", True), - ("my_fun(a=1, b=3)", True), - ("my_fun(1, 2)", True), - ("my_fun(1, b=2)", True), - ("my_fun(a=1, b=2)", True), - ], -) -def test_test_function_args(stu, passes): - s = setup_state(stu, "my_fun(1, b = 2)", pec="def my_fun(a, b): pass") - with helper.verify_sct(passes): - s.test_function("my_fun", args=[0], keywords=[]) - - -@pytest.mark.parametrize( - "stu, passes", - [ - ("", False), - ("my_fun(2, 2)", False), - ("my_fun(2, b=2)", True), - ("my_fun(a=2, b=2)", True), - ("my_fun(1, 3)", False), - ("my_fun(1, b=3)", False), - ("my_fun(a=1, b=3)", False), - ("my_fun(1, 2)", False), - ("my_fun(1, b=2)", True), - ("my_fun(a=1, b=2)", True), - ], -) -def test_test_function_keywords(stu, passes): - s = setup_state(stu, "my_fun(1, b = 2)", pec="def my_fun(a, b): pass") - with helper.verify_sct(passes): - s.test_function("my_fun", args=[], keywords=["b"]) - - -@pytest.mark.parametrize( - "stu, do_eval, passes", - [ - ("round(1)", True, True), - ("round(a)", True, True), - ("round(b)", True, False), - ("round(b - 1)", True, True), - ("round(1)", False, False), - ("round(a)", False, True), - ("round(b)", False, False), - ("round(b - 1)", False, False), - ("a=123; round(a)", False, True), - ], -) -def test_test_function_do_eval(stu, do_eval, passes): - s = setup_state(stu, "round(a)", pec="a,b = 1,2") - with helper.verify_sct(passes): - s.test_function("round", do_eval=do_eval) - - -@pytest.mark.parametrize( - "stu, passes", [("print(1)", True), ("print('1')", True), ("print(5)", False)] -) -def test_test_function_print(stu, passes): - s = setup_state(stu, "print(1)") - with helper.verify_sct(passes): - s.test_function("print") - - -# Old implementation: test_function_v2 ---------------------------------------- - - -@pytest.mark.parametrize( - "stu, passes", - [ - ("", False), - ("my_fun(2, 2)", False), - ("my_fun(2, b=2)", False), - ("my_fun(a=2, b=2)", False), - ("my_fun(1, 3)", False), - ("my_fun(1, b=3)", False), - ("my_fun(a=1, b=3)", False), - ("my_fun(1, 2)", True), # test_function_v2 is better - ("my_fun(1, b=2)", True), # test_function_v2 is better - ("my_fun(a=1, b=2)", True), - ], -) -def test_test_function_v2_basic(stu, passes): - s = setup_state(stu, "my_fun(a = 1, b = 2)", pec="def my_fun(a, b): pass") - with helper.verify_sct(passes): - s.test_function_v2("my_fun", params=["a", "b"]) - - -@pytest.mark.parametrize( - "stu, passes", - [ - ("", False), - ("my_fun(2, 2)", False), - ("my_fun(2, b=2)", False), - ("my_fun(a=2, b=2)", False), - ("my_fun(1, 3)", True), - ("my_fun(1, b=3)", True), - ("my_fun(a=1, b=3)", True), - ("my_fun(1, 2)", True), - ("my_fun(1, b=2)", True), - ("my_fun(a=1, b=2)", True), - ], -) -def test_test_function_v2_params(stu, passes): - s = setup_state(stu, "my_fun(1, b = 2)", pec="def my_fun(a, b): pass") - with helper.verify_sct(passes): - s.test_function_v2("my_fun", params=["a"]) - - -@pytest.mark.parametrize( - "stu, do_eval, passes", - [ - ("round(1)", True, True), - ("round(a)", True, True), - ("round(b)", True, False), - ("round(b - 1)", True, True), - ("round(1)", False, False), - ("round(a)", False, True), - ("round(b)", False, False), - ("round(b - 1)", False, False), - ("a=123; round(a)", False, True), - ], -) -def test_test_function_v2_do_eval(stu, do_eval, passes): - s = setup_state(stu, "round(a)", pec="a,b = 1,2") - with helper.verify_sct(passes): - s.test_function_v2("round", params=["number"], do_eval=[do_eval]) - - -@pytest.mark.parametrize( - "stu, passes", [("print(1)", True), ("print('1')", True), ("print(5)", False)] -) -def test_test_function_v2_print(stu, passes): - s = setup_state(stu, "print(1)") - with helper.verify_sct(passes): - s.test_function_v2("print", params=["value"]) - - -@pytest.mark.parametrize( - "sct", - [ - "s.test_function_v2('round', params='number')", - "s.test_function_v2('round', params=[], do_eval=[True])", - "s.test_function_v2('round', params=[], params_not_specified_msg=['test'])", - "s.test_function_v2('round', params=[], incorrect_msg=['test'])", - ], -) -def test_test_function_v2_incorrect_usage(sct): - s = setup_state("", "") - with pytest.raises(InstructorError): - eval(sct) - - -def test_test_function_v2_no_sig(): - s = setup_state("np.arange(10)", "np.arange(10)", pec="import numpy as np") - # test_function_v2 sets signature=False if no params - s.test_function_v2("numpy.arange") - # check_function fails unless explicity setting signature=Fa - s.check_function("numpy.arange", signature=False) - with pytest.raises(InstructorError): - s.check_function("numpy.arange") - - def test_function_call_in_return(): code = "def my_func(a): return my_func_in_return(b)" sct = "Ex().check_function_def('my_func').check_body().check_function('my_func_in_return', signature=False)" diff --git a/tests/test_check_function_def.py b/tests/test_check_function_def.py index b8650108..a747db5c 100644 --- a/tests/test_check_function_def.py +++ b/tests/test_check_function_def.py @@ -73,8 +73,6 @@ def test_check_function_def_name(stu, passes): "sct", [ "Ex().check_function_def('shout').check_body().set_context('test').has_equal_output()", - "test_function_definition('shout', body = lambda: test_expression_output(context_vals = ['help']))", - "test_function_definition('shout', body = test_expression_output(context_vals = ['help']))", ], ) def test_old_ways_of_calling(sct): @@ -92,7 +90,6 @@ def test_old_ways_of_calling(sct): check_args('**kwargs').has_equal_part('name', msg='x') ) """, - "Ex().test_function_definition('my_fun')", ], ) def test_old_ways_of_calling_starargs(sct): @@ -135,7 +132,6 @@ def test_check_function_def_args(stu, passes): "sct", [ "Ex().check_function_def('f').has_equal_part_len('args', unequal_msg='wrong')", - "Ex().test_function_definition('f')", # does arg len checking internally ], ) @pytest.mark.parametrize( @@ -201,24 +197,6 @@ def test_check_call_error_types(): s.check_function_def("test").check_call("f()").has_equal_error() -@pytest.mark.parametrize( - "sct", - [ - "Ex().test_function_definition('my_fun', results=[[1]])", - "Ex().test_function_definition('my_fun', results=[(1,)])", - "Ex().test_function_definition('my_fun', outputs=[[1]])", - "Ex().test_function_definition('my_fun', outputs=[(1,)])", - "Ex().test_function_definition('my_fun', errors=[['1']])", - "Ex().test_function_definition('my_fun', errors=[('1',)])", - "Ex().test_function_definition('my_fun', errors=['1'])", - ], -) -def test_check_call_old_way_of_calling(sct): - code = "def my_fun(a):\n print(a + 2)\n return a + 2" - res = helper.run({"DC_CODE": code, "DC_SOLUTION": code, "DC_SCT": sct}) - assert res["correct"] - - # Lambdas --------------------------------------------------------------------- diff --git a/tests/test_check_if_else.py b/tests/test_check_if_else.py index c226f3a7..67098ab7 100644 --- a/tests/test_check_if_else.py +++ b/tests/test_check_if_else.py @@ -6,28 +6,6 @@ "sct", [ """ -def condition_test(): - test_expression_result({"offset": 7}) - test_expression_result({"offset": 8}) - test_expression_result({"offset": 9}) -test_if_else(index=1, - test = condition_test, - body = lambda: test_student_typed(r'x\s*=\s*5'), - orelse = lambda: test_function('round')) - """, - """ -condition_test = [ - test_expression_result({"offset": 7}), - test_expression_result({"offset": 8}), - test_expression_result({"offset": 9}) -] - -test_if_else(index=1, - test = condition_test, - body = test_student_typed(r'x\s*=\s*5'), - orelse = test_function('round')) - """, - """ Ex().check_if_else().multi( check_test().multi([ set_env(offset = i).has_equal_value() for i in range(7,10) ]), check_body().has_code(r'x\s*=\s*5'), @@ -64,53 +42,6 @@ def test_check_if_else_basic(sct, stu, passes): "sct", [ """ -def test_test(): - test_expression_result({"offset": 7}) - test_expression_result({"offset": 8}) - test_expression_result({"offset": 9}) - -def body_test(): - test_student_typed('5') - -def orelse_test(): - def test_test2(): - test_expression_result({"offset": 4}) - test_expression_result({"offset": 5}) - test_expression_result({"offset": 6}) - def body_test2(): - test_student_typed('7') - def orelse_test2(): - test_function('round') - test_if_else(index = 1, - test = test_test2, - body = body_test2, - orelse = orelse_test2) - -test_if_else(index=1, - test=test_test, - body=body_test, - orelse=orelse_test) - """, - """ -test_if_else(index=1, - test=[ - test_expression_result({"offset": 7}), - test_expression_result({"offset": 8}), - test_expression_result({"offset": 9}) - ], - body=test_student_typed('5'), - orelse=test_if_else(index = 1, - test=[ - test_expression_result({"offset": 4}), - test_expression_result({"offset": 5}), - test_expression_result({"offset": 6}) - ], - body = test_student_typed('7'), - orelse = test_function('round') - ) -) - """, - """ Ex().check_if_else().multi( check_test().multi([ set_env(offset = i).has_equal_value() for i in range(7, 10) ]), check_body().has_code(r'x\s*=\s*5'), diff --git a/tests/test_check_list_comp.py b/tests/test_check_list_comp.py index b479bc3e..c77aa92c 100644 --- a/tests/test_check_list_comp.py +++ b/tests/test_check_list_comp.py @@ -48,178 +48,9 @@ def test_check_list_comp_basic(stu, passes): ) -@pytest.mark.parametrize( - "stu, passes, patt, lines", - [ - ( - "", - False, - "The system wants to check the first list comprehension but hasn't found it.", - [], - ), - ( - "[key for key in x.keys()]", - False, - "Check the first list comprehension. Did you correctly specify the iterable part?", - [1, 1, 17, 24], - ), - ( - "[a + str(b) for a,b in x.items()]", - False, - "Check the first list comprehension. Have you used the correct iterator variables?", - [1, 1, 17, 19], - ), - ( - "[key + '_' + str(val) for key,val in x.items()]", - False, - "Did you correctly specify the body?", - [1, 1, 2, 21], - ), - ( - "[key + str(val) for key,val in x.items()]", - False, - "Check the first list comprehension. Have you used 2 ifs?", - [], - ), - ( - "[key + str(val) for key,val in x.items() if hasattr(key, 'test') if hasattr(key, 'test')]", - False, - "Did you correctly specify the first if? Did you call isinstance()?", - [1, 1, 45, 64], - ), - ( - "[key + str(val) for key,val in x.items() if isinstance(key, str) if hasattr(key, 'test')]", - False, - "Did you correctly specify the second if? Did you call isinstance()?", - [1, 1, 69, 88], - ), - ( - "[key + str(val) for key,val in x.items() if isinstance(key, str) if isinstance(key, str)]", - False, - "Did you correctly specify the argument obj? Expected val, but got key.", - [1, 1, 80, 82], - ), - ( - "[key + str(val) for key,val in x.items() if isinstance(key, str) if isinstance(val, str)]", - True, - "Great", - [], - ), - ], -) -def test_test_list_comp_messaging(stu, passes, patt, lines): - pec = "x = {'a': 2, 'b':3, 'c':4, 'd':'test'}" - sol = "[key + str(val) for key,val in x.items() if isinstance(key, str) if isinstance(val, int)]" - sct = """ -test_list_comp(index=1, - not_called_msg=None, - comp_iter=lambda: test_expression_result(), - iter_vars_names=True, - incorrect_iter_vars_msg=None, - body=lambda: test_expression_result(context_vals = ['a', 2]), - ifs=[lambda: test_function_v2('isinstance', params = ['obj'], do_eval = [False]), - lambda: test_function_v2('isinstance', params = ['obj'], do_eval = [False])], - insufficient_ifs_msg=None) - """ - res = helper.run({"DC_PEC": pec, "DC_CODE": stu, "DC_SOLUTION": sol, "DC_SCT": sct}) - assert res["correct"] == passes - assert patt in res["message"] - if lines: - helper.with_line_info(res, *lines) - - -@pytest.mark.parametrize( - "stu, passes, patt, lines", - [ - ("", False, "notcalled", []), - ("[key for key in x.keys()]", False, "iterincorrect", [1, 1, 17, 24]), - ( - "[a + str(b) for a,b in x.items()]", - False, - "incorrectitervars", - [1, 1, 17, 19], - ), - ( - "[key + '_' + str(val) for key,val in x.items()]", - False, - "bodyincorrect", - [1, 1, 2, 21], - ), - ( - "[key + str(val) for key,val in x.items()]", - False, - "insufficientifs", - [], - ), # [1, 1, 2, 41] doesn't work... - ( - "[key + str(val) for key,val in x.items() if hasattr(key, 'test') if hasattr(key, 'test')]", - False, - "notcalled1", - [1, 1, 45, 64], - ), - ( - "[key + str(val) for key,val in x.items() if isinstance(key, str) if hasattr(key, 'test')]", - False, - "notcalled2", - [1, 1, 69, 88], - ), - ( - "[key + str(val) for key,val in x.items() if isinstance(key, str) if isinstance(key, str)]", - False, - "incorrect2", - [1, 1, 80, 82], - ), - ( - "[key + str(val) for key,val in x.items() if isinstance(key, str) if isinstance(val, str)]", - True, - "Great", - [], - ), - ], -) -def test_test_list_comp_custom_messaging(stu, passes, patt, lines): - pec = "x = {'a': 2, 'b':3, 'c':4, 'd':'test'}" - sol = "[key + str(val) for key,val in x.items() if isinstance(key, str) if isinstance(val, int)]" - sct = """ -test_list_comp(index=1, - not_called_msg='notcalled', - comp_iter=lambda: test_expression_result(incorrect_msg = 'iterincorrect'), - iter_vars_names=True, - incorrect_iter_vars_msg='incorrectitervars', - body=lambda: test_expression_result(context_vals = ['a', 2], incorrect_msg = 'bodyincorrect'), - ifs=[lambda: test_function_v2('isinstance', params = ['obj'], do_eval = [False], not_called_msg = 'notcalled1', incorrect_msg = 'incorrect2'), - lambda: test_function_v2('isinstance', params = ['obj'], do_eval = [False], not_called_msg = 'notcalled2', incorrect_msg = 'incorrect2')], - insufficient_ifs_msg='insufficientifs') - """ - res = helper.run({"DC_PEC": pec, "DC_CODE": stu, "DC_SOLUTION": sol, "DC_SCT": sct}) - assert res["correct"] == passes - assert patt in res["message"] - if lines: - helper.with_line_info(res, *lines) - - -@pytest.mark.parametrize( - "stu, passes", - [ - ("[[col + 1 for col in range(5)] for row in range(5)]", False), - ("[[col for col in range(5)] for row in range(5)]", True), - ], -) -def test_list_comp_nested(stu, passes): - res = helper.run( - { - "DC_CODE": stu, - "DC_SOLUTION": "[[col for col in range(5)] for row in range(5)]", - "DC_SCT": "test_list_comp(1, body = lambda: test_list_comp(1, body = lambda: test_expression_result(context_vals = [4])))", - } - ) - assert res["correct"] == passes - - @pytest.mark.parametrize( "sct", [ - "test_list_comp(1, iter_vars_names=False)", "Ex().check_list_comp(0).has_context()", ], ) @@ -236,7 +67,7 @@ def test_list_iter_vars(sct, stu, passes): "DC_SCT": sct, } ) - res["correct"] == passes + assert res["correct"] == passes # TODO diff --git a/tests/test_check_logic.py b/tests/test_check_logic.py index cfc3701a..cfa9f8a1 100644 --- a/tests/test_check_logic.py +++ b/tests/test_check_logic.py @@ -43,52 +43,10 @@ def test_check_or(sct, passes): assert output["correct"] == passes -@pytest.mark.parametrize( - "sct, passes", - [ - ("test_or(lambda: test_student_typed('a'))", True), - ("test_or(test_student_typed('a'))", True), - ("Ex().test_or(has_code('a'))", True), - ("Ex().test_or(test_student_typed('a'))", True), - ( - "test_or(lambda: test_student_typed('a'), lambda: test_student_typed('b'))", - True, - ), - ("test_or(test_student_typed('a'), test_student_typed('b'))", True), - ("Ex().test_or(has_code('a'), has_code('b'))", True), - ("Ex().test_or(test_student_typed('a'), test_student_typed('b'))", True), - ( - "test_or(lambda: test_student_typed('a'), lambda: test_student_typed('b'))", - True, - ), - ("test_or(test_student_typed('a'), test_student_typed('b'))", True), - ("Ex().test_or(has_code('a'), has_code('b'))", True), - ("Ex().test_or(test_student_typed('a'), test_student_typed('b'))", True), - ("test_or(lambda: test_student_typed('b'))", False), - ("test_or(test_student_typed('b'))", False), - ("Ex().test_or(has_code('b'))", False), - ("Ex().test_or(test_student_typed('b'))", False), - ( - "test_or(lambda: test_student_typed('b'), lambda: test_student_typed('c'))", - False, - ), - ("test_or(test_student_typed('b'), test_student_typed('c'))", False), - ("Ex().test_or(has_code('b'), has_code('c'))", False), - ("Ex().test_or(test_student_typed('b'), test_student_typed('c'))", False), - ], -) -def test_test_or(sct, passes): - data = {"DC_CODE": "'a'", "DC_SCT": sct} - output = helper.run(data) - assert output["correct"] == passes - - @pytest.mark.parametrize( "sct", [ "Ex().check_or(has_code('a', not_typed_msg = 'a'), check_or(has_code('b'), has_code('c')))", - "Ex().test_or(has_code('a', not_typed_msg = 'a'), F().test_or(has_code('b'), has_code('c')))", - "test_or(test_student_typed('a', not_typed_msg = 'a'), test_or(test_student_typed('b'), test_student_typed('c')))", ], ) def test_nested_or(sct): @@ -148,89 +106,10 @@ def test_check_correct_force_diagnose(sct, passes, msg): assert output["message"] == msg -@pytest.mark.parametrize( - "sct, passes, msg", - [ - ( - "test_correct(lambda: test_student_typed('a'), lambda: test_student_typed('b'))", - True, - None, - ), - ("test_correct(test_student_typed('a'), test_student_typed('b'))", True, None), - ("Ex().test_correct(has_code('a'), has_code('b'))", True, None), - ( - "Ex().test_correct(test_student_typed('a'), test_student_typed('b'))", - True, - None, - ), - ( - "test_correct(lambda: test_student_typed('a'), lambda: test_student_typed('c'))", - True, - None, - ), - ("test_correct(test_student_typed('a'), test_student_typed('c'))", True, None), - ("Ex().test_correct(has_code('a'), has_code('c'))", True, None), - ( - "Ex().test_correct(test_student_typed('a'), test_student_typed('c'))", - True, - None, - ), - ( - "test_correct(lambda: test_student_typed('b'), lambda: test_student_typed('c', not_typed_msg='x'))", - False, - "x", - ), - ( - "test_correct(test_student_typed('b'), test_student_typed('c', not_typed_msg='x'))", - False, - "x", - ), - ( - "Ex().test_correct(has_code('b'), has_code('c', not_typed_msg='x'))", - False, - "x", - ), - ( - "Ex().test_correct(test_student_typed('b'), test_student_typed('c', not_typed_msg='x'))", - False, - "x", - ), - ( - "test_correct(lambda: test_student_typed('b', not_typed_msg='x'), lambda: test_student_typed('a'))", - False, - "x", - ), - ( - "test_correct(test_student_typed('b', not_typed_msg='x'), test_student_typed('a'))", - False, - "x", - ), - ( - "Ex().test_correct(has_code('b', not_typed_msg='x'), has_code('a'))", - False, - "x", - ), - ( - "Ex().test_correct(test_student_typed('b', not_typed_msg='x'), test_student_typed('a'))", - False, - "x", - ), - ], -) -def test_test_correct(sct, passes, msg): - data = {"DC_CODE": "'a'", "DC_SCT": sct} - output = helper.run(data) - assert output["correct"] == passes - if msg: - assert output["message"] == msg - - @pytest.mark.parametrize( "sct", [ "Ex().check_correct(has_code('a'), check_correct(has_code('b'), has_code('c', not_typed_msg = 'c')))", - "Ex().test_correct(has_code('a'), F().test_correct(has_code('b'), has_code('c', not_typed_msg = 'c')))", - "test_correct(test_student_typed('a'), test_correct(test_student_typed('b'), test_student_typed('c', not_typed_msg = 'c')))", ], ) def test_nested_correct(sct): diff --git a/tests/test_check_object.py b/tests/test_check_object.py index 1537c05d..a3d0d4e9 100644 --- a/tests/test_check_object.py +++ b/tests/test_check_object.py @@ -6,7 +6,6 @@ @pytest.mark.parametrize( "sct", [ - "test_object('x', undefined_msg='udm', incorrect_msg='icm')", "Ex().check_object('x', missing_msg='udm').has_equal_value(incorrect_msg='icm')", ], ) @@ -95,8 +94,6 @@ def test_is_instance(stu_code, passes): @pytest.mark.parametrize( "sct", [ - "test_data_frame('df', columns=['a'], undefined_msg='udm', not_data_frame_msg='ndfm', undefined_cols_msg='ucm', incorrect_msg='icm')", - "test_data_frame('df', columns=None, undefined_msg='udm', not_data_frame_msg='ndfm', undefined_cols_msg='ucm', incorrect_msg='icm')", """ import pandas as pd Ex().check_object('df', missing_msg='udm', expand_msg='').\ @@ -159,7 +156,6 @@ def test_check_keys(stu_code, passes): @pytest.mark.parametrize( "sct", [ - "Ex().test_data_frame('pivot')", "Ex().check_df('pivot').check_keys(('visitors', 'Austin')).has_equal_value()", ], ) diff --git a/tests/test_converters.py b/tests/test_converters.py index ddf35b7b..6eb20824 100644 --- a/tests/test_converters.py +++ b/tests/test_converters.py @@ -7,7 +7,7 @@ def test_excel(): data = { "DC_PEC": "import pandas as pd; from urllib.request import urlretrieve; urlretrieve('https://s3.amazonaws.com/assets.datacamp.com/production/course_998/datasets/battledeath.xlsx', 'battledeath.xlsx')", "DC_SOLUTION": "xl = pd.ExcelFile('battledeath.xlsx')", - "DC_SCT": "test_object('xl')", + "DC_SCT": "check_object('xl')", "DC_CODE": "xl = pd.ExcelFile('battledeath.xlsx')", } sct_payload = helper.run(data) diff --git a/tests/test_docs.py b/tests/test_docs.py index b4a4f381..8704becc 100644 --- a/tests/test_docs.py +++ b/tests/test_docs.py @@ -40,7 +40,7 @@ def test_compount_statement_if_fail(data1): @pytest.fixture def data2(): return { - "DC_SOLUTION": """ + "DC_SOLUTION": """ my_dict = {'a': 1, 'b': 2} for key, value in my_dict.items(): print(key + " - " + str(value)) @@ -90,7 +90,7 @@ def shout_echo(word1, echo=1): return shout_words """, "DC_SCT": """ -Ex().check_function_def('shout_echo').test_correct( +Ex().check_function_def('shout_echo').check_correct( multi( check_call("f('hey', 3)").has_equal_value(), check_call("f('hi', 2)").has_equal_value(), @@ -134,7 +134,7 @@ def counter(lst, key): return count """, "DC_SCT": """ -Ex().check_function_def('counter').test_correct( +Ex().check_function_def('counter').check_correct( multi( check_call("f([{'a': 1}], 'a')").has_equal_value(), check_call("f([{'b': 1}, {'b': 2}], 'b')").has_equal_value() diff --git a/tests/test_has_chosen.py b/tests/test_has_chosen.py index 35d5d817..9bd54573 100644 --- a/tests/test_has_chosen.py +++ b/tests/test_has_chosen.py @@ -11,11 +11,6 @@ ], ) def test_has_chosen(stu_code, passes, mess): - data = {"DC_CODE": stu_code, "DC_SCT": "test_mc(2, ['a', 'b', 'c'])"} - res = helper.run(data) - assert res["correct"] is passes - assert res["message"] == mess - data = {"DC_CODE": stu_code, "DC_SCT": "Ex().has_chosen(2, ['a', 'b', 'c'])"} res = helper.run(data) assert res["correct"] is passes diff --git a/tests/test_has_expr.py b/tests/test_has_expr.py index dca81dfe..37b27430 100644 --- a/tests/test_has_expr.py +++ b/tests/test_has_expr.py @@ -52,32 +52,13 @@ def __getstate__(self): s.has_equal_value(expr_code="print(a)") -@pytest.mark.parametrize( - "stu, passes", - [ - ("a = 0\nfor i in range(0): pass", False), - ("a = 0\nfor i in range(0): a = a - 1", False), - ("a = 0\nfor i in range(0): a = a + 1", True), - ], -) -def test_has_equal_value_old(stu, passes): - out = helper.run( - { - "DC_CODE": stu, - "DC_SOLUTION": "a = 0\nfor i in range(0): a = a + 1", - "DC_SCT": "test_for_loop(body = test_object_after_expression('a'))", - } - ) - out["correct"] == passes - - @pytest.mark.parametrize( "stu, passes", [("", False), ('x = {"a": 2}', False), ('x = {"a": 1}', True)] ) def test_has_equal_output_basic(stu, passes): s = setup_state(stu, 'x = {"a":1, "b":2, "c": 3}') with helper.verify_sct(passes): - s.test_expression_output(expr_code='print(x["a"])') + s.has_equal_output(expr_code='print(x["a"])') @pytest.mark.parametrize( diff --git a/tests/test_has_output.py b/tests/test_has_output.py index 96f01591..1c984b67 100644 --- a/tests/test_has_output.py +++ b/tests/test_has_output.py @@ -29,17 +29,3 @@ def test_has_output_pattern(stu, passes): s = setup_state(stu, "") with helper.verify_sct(passes): s.has_output("Hi, there!", pattern=False) - - -@pytest.mark.parametrize( - "stu, passes", - [ - ('print("Hi, there!")', True), - ('print("hi there!")', True), - ('print("Hello there")', False), - ], -) -def test_test_output_contains(stu, passes): - s = setup_state(stu, "") - with helper.verify_sct(passes): - s.test_output_contains(r"[H|h]i,*\s+there!") diff --git a/tests/test_messaging.py b/tests/test_messaging.py index aec922a6..6ed3d684 100644 --- a/tests/test_messaging.py +++ b/tests/test_messaging.py @@ -200,7 +200,7 @@ def test_check_function_pkg1(stu, patt): { "DC_SOLUTION": "import pandas as pd; pd.DataFrame({'a': [1, 2, 3]})", "DC_CODE": stu, - "DC_SCT": "test_function_v2('pandas.DataFrame')", + "DC_SCT": "Ex().check_function('pandas.DataFrame')", } ) assert not output["correct"] @@ -219,7 +219,7 @@ def test_check_function_pkg2(stu, patt): { "DC_SOLUTION": "import numpy as np; x = np.random.rand(1)", "DC_CODE": stu, - "DC_SCT": "test_function_v2('numpy.random.rand')", + "DC_SCT": "Ex().check_function('numpy.random.rand')", } ) assert not output["correct"] @@ -327,7 +327,6 @@ def test_check_object(stu, patt, cols, cole): @pytest.mark.parametrize( "sct", [ - "test_data_frame('df', columns=['a'])", "import pandas as pd; Ex().check_object('df', typestr = 'pandas DataFrame').is_instance(pd.DataFrame).check_keys('a').has_equal_value()", ], ) @@ -743,11 +742,11 @@ def test_nesting(stu, patt): "DC_SOLUTION": "a = 1; b = a + 1; c = b + 1; print(c)", "DC_CODE": stu, "DC_SCT": """ -Ex().test_correct( +Ex().check_correct( has_printout(0), - F().test_correct( + F().check_correct( check_object('c').has_equal_value(), - F().test_correct( + F().check_correct( check_object('b').has_equal_value(), check_object('a').has_equal_value() ) diff --git a/tests/test_spec.py b/tests/test_spec.py index 4bce58eb..08ba4857 100644 --- a/tests/test_spec.py +++ b/tests/test_spec.py @@ -42,23 +42,14 @@ def test_f_chain(sct): ( None, """ -te = test_expression_result(extra_env={'aa':2}, incorrect_msg='unequal') -Ex().multi(test_list_comp(body=te)) # spec 1 inside multi -Ex().check_list_comp(0).check_body().multi(te) # half of each spec Ex().check_list_comp(0).check_body().set_context(aa=2).has_equal_value('unequal') # full spec 2 -test_list_comp(body=te) # full spec 1 """, True, None, ), - # TODO: this test fails because spec1 tests are run after spec2 tests, - # even if they come first in the SCT script, due to building the tree - # for spec1 tests but not spec2 (which are run immediately) ( "for aa in range(3): aa", """ -test_list_comp(body=test_expression_result(expr_code = 'aa', incorrect_msg='unequal')) -Ex().check_list_comp(0).check_body().multi(test_expression_result(incorrect_msg='unequal')) Ex().check_list_comp(0).check_body().has_equal_value('unequal') """, False, @@ -67,11 +58,10 @@ def test_f_chain(sct): ( "[aa for aa in range(2)]", """ -Ex().test_list_comp(body=test_expression_result(extra_env={'aa': 2}, incorrect_msg = 'spec1')) Ex().check_list_comp(0).check_body().set_context(aa=2).has_equal_value('spec2') """, False, - "spec1", + "spec2", ), ], ) diff --git a/tests/test_test_compound_statement.py b/tests/test_test_compound_statement.py deleted file mode 100644 index b392fa7a..00000000 --- a/tests/test_test_compound_statement.py +++ /dev/null @@ -1,117 +0,0 @@ -import pytest -import tests.helper as helper -from pythonwhat.test_exercise import setup_state -from pythonwhat.sct_syntax import v2_check_functions - -globals().update(v2_check_functions) - -# Check for loop -------------------------------------------------------------- - - -@pytest.mark.parametrize( - "sct", - [ - "test_for_loop(for_iter=lambda: test_expression_result(), body=lambda: test_expression_output())", - "Ex().test_for_loop(for_iter=lambda: test_expression_result(), body=lambda: test_expression_output())", - "Ex().check_for_loop().multi(check_iter().has_equal_value(), check_body().has_equal_output())", - ], -) -@pytest.mark.parametrize( - "stu, passes", - [ - ("", False), - ("for i in range(4): pass", False), - ("for i in range(3): pass", False), - ("for i in range(3): print(i)", True), - ("for j in range(3): print(j)", True), - ], -) -def test_for_loop(sct, stu, passes): - res = helper.run( - {"DC_CODE": stu, "DC_SOLUTION": "for i in range(3): print(i)", "DC_SCT": sct} - ) - assert res["correct"] == passes - - -@pytest.mark.parametrize( - "stu, passes", - [ - ("", False), - ("for i in range(3):\n pass", False), - ("for i in range(3):\n for j in range(3):\n pass", False), - ("for i in range(3):\n for j in range(4):\n pass", False), - ("for i in range(3):\n for j in range(4):\n print(i + j)", True), - ("for j in range(3):\n for i in range(4):\n print(i + j)", True), - ], -) -def test_for_loop_nested(stu, passes): - s = setup_state(stu, "for i in range(3):\n for j in range(4):\n print(i + j)") - with helper.verify_sct(passes): - s.check_for_loop().multi( - check_iter().has_equal_value(), - check_body() - .set_context(2) - .check_for_loop() - .multi(check_iter(), check_body().set_context(3).has_equal_output()), - ) - - -@pytest.mark.parametrize( - "stu, passes", - [ - ("for i in range(1):\n pass", False), - ("for i in range(1):\n pass\nfor j in range(2): pass", False), - ("for i in range(3):\n pass\nfor j in range(4): pass", False), - ("for i in range(3):\n pass\nfor j in range(4): print(j)", True), - ("for i in range(3):\n pass\nfor i in range(4): print(i)", True), - ], -) -def test_two_for_loops(stu, passes): - s = setup_state(stu, "for i in range(1):\n pass\nfor j in range(4): print(j)") - with helper.verify_sct(passes): - s.check_for_loop(index=1).multi( - check_iter().has_equal_value(), - check_body().set_context(2).has_equal_output(), - ) - - -@pytest.mark.parametrize( - "stu, exact, passes", - [ - ("for i in range(2): pass", False, True), - ("for j in range(2): pass", False, True), - ("for i in range(2): pass", True, True), - ("for j in range(2): pass", True, False), - ], -) -def test_has_context(stu, exact, passes): - s = setup_state(stu, "for i in range(2): pass") - with helper.verify_sct(passes): - s.check_for_loop().check_body().has_context(exact_names=exact) - - -# Check while loop ------------------------------------------------------------ - - -@pytest.mark.parametrize( - "sct", - [ - "test_while_loop(test = lambda: test_student_typed('3'), body = lambda: test_student_typed('print'))", - "Ex().test_while_loop(test = test_student_typed('3'), body = test_student_typed('print'))", - "Ex().check_while().multi(check_test().has_code('3'), check_body().has_code('print'))", - ], -) -@pytest.mark.parametrize( - "stu, passes", - [ - ("", False), - ("while False: pass", False), - ("while 3 > 4: pass", False), - ("while 3 > 4: print(2)", True), - ], -) -def test_while_loop(sct, stu, passes): - res = helper.run( - {"DC_CODE": stu, "DC_SOLUTION": "while 3 > 4: print(2)", "DC_SCT": sct} - ) - assert res["correct"] == passes diff --git a/tests/test_test_exercise.py b/tests/test_test_exercise.py deleted file mode 100644 index 6438a27f..00000000 --- a/tests/test_test_exercise.py +++ /dev/null @@ -1,86 +0,0 @@ -import json - -import pytest -import tests.helper as helper - - -@pytest.fixture(scope="session", autouse=True) -def log_calls(): - yield - print("Output test data") - with open("docs/test_data.json", "w") as write_file: - json.dump(helper.test_data, write_file) - - -def test_normal_pass(): - data = { - "DC_PEC": "#no pec", - "DC_CODE": "x = 4", - "DC_SOLUTION": "x = 4", - "DC_SCT": 'test_object("x")\nsuccess_msg("nice")', - } - output = helper.run(data) - assert output["correct"] - assert output["message"] == "nice" - - -def test_normal_fail(): - data = { - "DC_PEC": "#no pec", - "DC_CODE": "x = 4", - "DC_SOLUTION": "x = 6", - "DC_SCT": 'test_object("x")\nsuccess_msg("nice")', - } - output = helper.run(data) - assert not output["correct"] - - -def test_normal_error(): - data = { - "DC_PEC": "#no pec", - "DC_CODE": "x = y", - "DC_SOLUTION": "x = 6", - "DC_SCT": "# no sct", - } - output = helper.run(data) - assert not output["correct"] - assert "Your code generated an error." in output["message"] - - -def test_syntax_error(): - data = { - "DC_PEC": "# no pec", - "DC_CODE": 'print "yolo"', - "DC_SOLUTION": "x = 6", - "DC_SCT": 'test_object("x")', - } - output = helper.run(data) - assert not output["correct"] - assert "Your code can not be executed due to a syntax error" in output["message"] - - -def test_indentation_error(): - data = { - "DC_PEC": "# no pec", - "DC_CODE": ' print("yolo")', - "DC_SOLUTION": "x = 6", - "DC_SCT": 'test_object("x")', - } - output = helper.run(data) - assert not output["correct"] - assert ( - "Your code could not be parsed due to an error in the indentation" - in output["message"] - ) - - -def test_enrichment_error(): - data = { - "DC_PEC": "# no pec", - "DC_CODE": "", - "DC_SOLUTION": "x = 6", - "DC_SCT": 'test_object("x")', - } - output = helper.run(data) - assert not output["correct"] - # assert not "line_start" in output diff --git a/tests/test_test_object_accessed.py b/tests/test_test_object_accessed.py deleted file mode 100644 index 6e76588b..00000000 --- a/tests/test_test_object_accessed.py +++ /dev/null @@ -1,80 +0,0 @@ -import pytest -import tests.helper as helper - - -@pytest.mark.parametrize( - "sct, passes, mess", - [ - ('test_object_accessed("arr")', True, None), - ('test_object_accessed("ar")', False, None), - ('test_object_accessed("arr", times=2)', True, None), - ( - 'test_object_accessed("arr", times=3)', - False, - "Have you accessed arr at least three times?", - ), - ( - 'test_object_accessed("arr", times=3, not_accessed_msg="silly")', - False, - "silly", - ), - ('test_object_accessed("arr.shape")', True, None), - ( - 'test_object_accessed("arr.shape", times=2)', - False, - "Have you accessed arr.shape at least twice?", - ), - ( - 'test_object_accessed("arr.shape", times=2, not_accessed_msg="silly")', - False, - "silly", - ), - ( - 'test_object_accessed("arr.dtype")', - False, - "Have you accessed arr.dtype?", - ), - ('test_object_accessed("arr.dtype", not_accessed_msg="silly")', False, "silly"), - ('test_object_accessed("math.e")', True, None), - ( - 'test_object_accessed("math.pi")', - False, - "Have you accessed m.pi?", - ), - ('test_object_accessed("math.pi", not_accessed_msg="silly")', False, "silly"), - ], -) -def test_test_object_accessed(sct, passes, mess): - res = helper.run( - { - "DC_CODE": """ -import numpy as np -import math as m -arr = np.array([1, 2, 3]) -x = arr.shape -print(arr.data) -print(m.e) - """, - "DC_SOLUTION": "# not used", - "DC_SCT": sct, - } - ) - assert res["correct"] == passes - if mess: - assert res["message"] == mess - - -# ObjectAccess parser ------------------------------------------------------------- - -from pythonwhat.parsing import ObjectAccessParser -import ast - - -@pytest.mark.parametrize( - "code", - ["x.a[1]", "x.a", "print(x.a)", "print(kw = x.a)", "(x.a, y.a)", "[x.a, y.a]"], -) -def test_object_access_parser(code): - p = ObjectAccessParser() - p.visit(ast.parse(code)) - assert "x.a" in p.out diff --git a/tests/test_test_with.py b/tests/test_test_with.py deleted file mode 100644 index 6e4719aa..00000000 --- a/tests/test_test_with.py +++ /dev/null @@ -1,305 +0,0 @@ -import pytest -import tests.helper as helper - - -@pytest.mark.parametrize( - "sct, passes, patt, lines", - [ - ( - "test_with(1, body = lambda: [test_function('print', index = i + 1) for i in range(3)])", - False, - "Check your third call of print(). Did you correctly specify the first argument? Expected something different.", - [6, 6, 11, 16], - ), - ( - "test_with(2, body = lambda: test_for_loop(1, body = lambda: test_if_else(1, body = lambda: test_function('print'))))", - True, - None, - None, - ), - ( - "test_with(1, body = [test_function('print', index = i + 1) for i in range(3)])", - False, - None, - [6, 6, 11, 16], - ), - ( - "test_with(2, body = test_for_loop(1, body = test_if_else(1, body = test_function('print'))))", - True, - None, - None, - ), - ( - """ -for_test = test_for_loop(1, body = test_if_else(1, body = test_function('print'))) -Ex().check_with(1).check_body().with_context(for_test) - """, - True, - None, - None, - ), - ( - "Ex().check_with(0).check_body().with_context([test_function('print', index = i+1) for i in range(3)])", - False, - "Check your third call of print()", - [6, 6, 11, 16], - ), - ( - """ -# since the print func is being tested w/o SCTs setting any variables, don't need with_context -for_test = test_for_loop(1, body = test_if_else(1, body = test_function('print'))) -Ex().check_with(1).check_body().multi(for_test) - """, - True, - None, - None, - ), - ], -) -def test_test_with_1(sct, passes, patt, lines): - res = helper.run( - { - "DC_PEC": """ -from urllib.request import urlretrieve; urlretrieve('http://s3.amazonaws.com/assets.datacamp.com/production/course_998/datasets/moby_opens.txt', 'moby_dick.txt') - """, - "DC_CODE": """ -# Read & print the first 3 lines -with open('moby_dick.txt') as file: - print(file.readline()) - print(file.readline()) - print('test') - -# The rows that you wish to print -I = [0,1,3,5,6,7,8,9] - -# Print out these rows -with open('moby_dick.txt') as file: - for i, row in enumerate(file): - if i in I: - print(row) - """, - "DC_SOLUTION": """ -# Read & print the first 3 lines -with open('moby_dick.txt') as file: - print(file.readline()) - print(file.readline()) - print(file.readline()) - -# The rows that you wish to print -I = [0,1,3,5,6,7,8,9] - -# Print out these rows -with open('moby_dick.txt') as file: - for i, row in enumerate(file): - if i in I: - print(row) -""", - "DC_SCT": sct, - } - ) - assert res["correct"] == passes - if patt: - assert patt in res["message"] - if lines: - helper.with_line_info(res, *lines) - - -@pytest.mark.parametrize( - "sct, passes, patt, lines", - [ - ( - "test_with(1, context_vals=True)", - False, - "Check the first with statement. Make sure to use the correct number of context variables. It seems you defined too many.", - [3, 6, 1, 17], - ), - ( - "test_with(2, context_vals=True)", - False, - "Check the second with statement. Did you correctly specify the first context? Make sure to use the correct context variable names. Was expecting file but got not_file.", - [12, 15, 1, 22], - ), - ], -) -def test_test_with_2(sct, passes, patt, lines): - res = helper.run( - { - "DC_PEC": """ -from urllib.request import urlretrieve; urlretrieve('http://s3.amazonaws.com/assets.datacamp.com/production/course_998/datasets/moby_opens.txt', 'moby_dick.txt') -""", - "DC_CODE": """ -# Read & print the first 3 lines -with open('moby_dick.txt') as file, open('moby_dick.txt'): - print(file.readline()) - print(file.readline()) - print('test') - -# The rows that you wish to print -I = [0,1,3,5,6,7,8,9] - -# Print out these rows -with open('moby_dick.txt') as not_file: - for i, row in enumerate(not_file): - if i in I: - print(row) - """, - "DC_SOLUTION": """ -# Read & print the first 3 lines -with open('moby_dick.txt') as file: - print(file.readline()) - print(file.readline()) - print(file.readline()) - -# The rows that you wish to print -I = [0,1,3,5,6,7,8,9] - -# Print out these rows -with open('moby_dick.txt') as file: - for i, row in enumerate(file): - if i in I: - print(row) -""", - "DC_SCT": sct, - } - ) - assert res["correct"] == passes - if patt: - assert patt in res["message"] - if lines: - helper.with_line_info(res, *lines) - - -@pytest.mark.parametrize( - "sct, passes, patt, lines", - [ - ("test_with(1, context_tests=lambda: test_function('open'))", True, None, None), - ( - """ -test_with(1, context_tests=[ - lambda: test_function('open'), - lambda: test_function('open')]) - """, - False, - "Check the first with statement. Make sure to use the correct number of context variables. It seems you defined too little.", - [3, 6, 1, 17], - ), - ( - """ -test_with(2, context_tests=[ - lambda: test_function('open'), - lambda: test_function('open')]) - """, - False, - "Check your call of open().", - [12, 12, 46, 60], - ), - ], -) -def test_test_with_3(sct, passes, patt, lines): - res = helper.run( - { - "DC_PEC": """ -from urllib.request import urlretrieve; urlretrieve('http://s3.amazonaws.com/assets.datacamp.com/production/course_998/datasets/moby_opens.txt', 'moby_dick.txt') -from urllib.request import urlretrieve; urlretrieve('https://s3.amazonaws.com/assets.datacamp.com/production/course_998/datasets/moby_opens.txt', 'not_moby_dick.txt') - """, - "DC_CODE": """ -# Read & print the first 3 lines -with open('moby_dick.txt') as file: - print(file.readline()) - print(file.readline()) - print('test') - -# The rows that you wish to print -I = [0,1,3,5,6,7,8,9] - -# Print out these rows -with open('moby_dick.txt') as not_file, open('moby_dick.txt') as file: - for i, row in enumerate(not_file): - if i in I: - print(row) - """, - "DC_SOLUTION": """ -# Read & print the first 3 lines -with open('moby_dick.txt') as file, open('moby_dick.txt'): - print(file.readline()) - print(file.readline()) - print(file.readline()) - -# The rows that you wish to print -I = [0,1,3,5,6,7,8,9] - -# Print out these rows -with open('moby_dick.txt') as file, open('not_moby_dick.txt') as not_file: - for i, row in enumerate(file): - if i in I: - print(row) - """, - "DC_SCT": sct, - } - ) - assert res["correct"] == passes - if patt: - assert patt in res["message"] - if lines: - helper.with_line_info(res, *lines) - - -def test_test_with_destructuring(): - code = """ -with A() as (one, *others): - print(one) - print(others) -""" - res = helper.run( - { - "DC_PEC": """ -class A: - def __enter__(self): return [1,2, 3] - def __exit__(self, *args, **kwargs): return - """, - "DC_SOLUTION": code, - "DC_CODE": code, - "DC_SCT": """ -test_with(1, body=[test_function('print'), test_function('print')]) -""", - } - ) - assert res["correct"] - - -@pytest.mark.parametrize( - "sct, stu, passes", - [ - ( - "Ex().check_with(0).has_context()", - "with StringIO() as f1, StringIO() as f2: pass", - True, - ), - ("Ex().check_with(0).has_context()", "with StringIO() as f1: pass", False), - ( - "Ex().check_with(0).has_context(exact_names=True)", - "with StringIO() as f3, StringIO() as f4: pass", - False, - ), - ( - "Ex().check_with(0).check_context(0).has_context()", - "with StringIO() as f1, StringIO() as f2: pass", - True, - ), - ( - "Ex().check_with(0).check_context(0).has_context(exact_names=True)", - "with StringIO() as f3: pass", - False, - ), - ], -) -def test_test_with_has_context(sct, stu, passes): - res = helper.run( - { - "DC_PEC": "from io import StringIO", - "DC_SOLUTION": "with StringIO() as f1, StringIO() as f2: pass", - "DC_CODE": stu, - "DC_SCT": sct, - } - ) - assert res["correct"] == passes diff --git a/tests/test_v2_only.py b/tests/test_v2_only.py deleted file mode 100644 index 58fac326..00000000 --- a/tests/test_v2_only.py +++ /dev/null @@ -1,61 +0,0 @@ -import pytest -import tests.helper as helper -import importlib - - -def relooooad(): - import pythonwhat.sct_syntax - importlib.reload(pythonwhat.sct_syntax) - - -@pytest.fixture -def data(): - return {"DC_CODE": "x = round(4)", "DC_SOLUTION": "x = round(5)"} - - -@pytest.mark.parametrize( - "sct", - [ - "test_object('x')", - "Ex().test_object('x')", - "test_function('round')", - "Ex().check_object('x').has_equal_value()", - "Ex() >> check_object('x').has_equal_value()", - "x = check_object('x').has_equal_value(); Ex() >> x", - ], -) -def test_without_env_all_works(data, sct): - data["DC_SCT"] = sct - with helper.set_v2_only_env(""): - relooooad() - sct_payload = helper.run(data) - assert not sct_payload["correct"] - - -@pytest.mark.parametrize( - "sct, should_err", - [ - ("test_object('x')", True), - ("test_function('round')", True), - ("Ex().test_object('x')", True), - ("Ex().test_or(check_object('x').has_equal_value())", True), - ("Ex().check_or(test_object('x'))", True), - ("Ex().check_object('x').has_equal_value()", False), - ("Ex() >> check_object('x').has_equal_value()", False), - ( - "Ex().check_or(check_object('x').has_equal_value(), check_object('x').has_equal_value())", - False, - ), - ("x = check_object('x').has_equal_value(); Ex() >> x", False), - ], -) -def test_with_env_old_fail(data, sct, should_err): - data["DC_SCT"] = sct - with helper.set_v2_only_env("1"): - relooooad() - if should_err: - with pytest.raises((NameError, AttributeError)): - sct_payload = helper.run(data) - else: - sct_payload = helper.run(data) - assert not sct_payload["correct"]