Skip to content

Commit e8e13eb

Browse files
committed
expand has_equal_ast to see if student code contains solution, manual code
1 parent cd345a6 commit e8e13eb

2 files changed

Lines changed: 60 additions & 4 deletions

File tree

pythonwhat/check_funcs.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -397,19 +397,28 @@ def call(args,
397397
from pythonwhat.tasks import ReprFail, UndefinedValue
398398
from pythonwhat import utils
399399

400-
def has_equal_ast(incorrect_msg="FMT: Your code does not seem to match the solution.", state=None):
400+
def has_equal_ast(incorrect_msg="FMT: Your code does not seem to match the solution.", code=None, exact=True, state=None):
401401
"""Test whether abstract syntax trees match between the student and solution code.
402402
403403
Args:
404404
incorrect_msg: message displayed when ASTs mismatch.
405+
code: optional code to use instead of the solution AST
406+
exact: whether the representations must match exactly. If false, the solution AST
407+
only needs to be contained within the student AST (similar to using test student typed).
405408
"""
406409
rep = Reporter.active_reporter
407410

408-
stu_rep = ast.dump(state.student_tree)
409-
sol_rep = ast.dump(state.solution_tree)
411+
parse_tree = lambda n: ast.dump(n.body[0] if isinstance(n, ast.Module) and len(n.body) == 1 else n)
412+
413+
stu_rep = parse_tree(state.student_tree)
414+
sol_rep = parse_tree(state.solution_tree if not code else ast.parse(code))
410415

411416
_msg = state.build_message(incorrect_msg)
412-
rep.do_test(EqualTest(stu_rep, sol_rep, Feedback(_msg, state.highlight)))
417+
418+
if exact:
419+
rep.do_test(EqualTest(stu_rep, sol_rep, Feedback(_msg, state.highlight)))
420+
elif not sol_rep in stu_rep:
421+
rep.do_test(Test(Feedback(_msg, state.highlight)))
413422

414423
return state
415424

tests/test_spec.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,53 @@ def test_fail(self):
172172
sct_payload = helper.run(self.data)
173173
self.assertFalse(sct_payload['correct'])
174174

175+
class TestHasEqualAst(unittest.TestCase):
176+
def setUp(self):
177+
self.data = {
178+
"DC_SOLUTION": """dict(a = "a").keys()""",
179+
"DC_CODE": """dict(a = 'a') .keys()"""}
180+
181+
def failing_submission(self):
182+
self.data["DC_CODE"] = "dict(A = 'a').keys(somearg = 2)"""
183+
sct_payload = helper.run(self.data)
184+
self.assertFalse(sct_payload['correct'])
185+
186+
def test_simple_pass(self):
187+
self.data["DC_SCT"] = "Ex().has_equal_ast()"
188+
sct_payload = helper.run(self.data)
189+
self.assertTrue(sct_payload['correct'])
190+
191+
def test_simple_fail(self):
192+
self.data["DC_SCT"] = "Ex().has_equal_ast()"
193+
self.failing_submission()
194+
195+
def test_function_pass(self):
196+
self.data["DC_SCT"] = "Ex().check_function('dict', 0, signature=False).has_equal_ast()"
197+
sct_payload = helper.run(self.data)
198+
self.assertTrue(sct_payload['correct'])
199+
200+
def test_function_fail(self):
201+
self.data["DC_SCT"] = "Ex().check_function('dict', 0, signature=False).has_equal_ast()"
202+
self.failing_submission()
203+
204+
def test_function_code_pass(self):
205+
self.data["DC_SCT"] = """Ex().has_equal_ast(code = 'dict(a = "a").keys()')"""
206+
sct_payload = helper.run(self.data)
207+
self.assertTrue(sct_payload['correct'])
208+
209+
def test_function_code_fail(self):
210+
self.data["DC_SCT"] = """Ex().has_equal_ast(code = 'dict(a = "a").keys()')"""
211+
self.failing_submission()
212+
213+
def test_exact_false_pass(self):
214+
self.data["DC_CODE"] = """dict(a = 'a').keys()\nprint('extra')"""
215+
self.data["DC_SCT"] = "Ex().has_equal_ast(exact=False)"
216+
sct_payload = helper.run(self.data)
217+
self.assertTrue(sct_payload['correct'])
218+
219+
def test_exact_false_fail(self):
220+
self.data["DC_SCT"] = "Ex().has_equal_ast(exact=False)"
221+
self.failing_submission()
175222

176223
class TestOverride(unittest.TestCase):
177224
"""

0 commit comments

Comments
 (0)