Skip to content

Commit 68ab857

Browse files
committed
finish test_object_accessed, fix datacamp#19
1 parent bf3183c commit 68ab857

9 files changed

Lines changed: 157 additions & 62 deletions

File tree

pythonwhat/State.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import ast
22
import inspect
3-
from pythonwhat.parsing import FunctionParser, IfParser, WhileParser, ForParser, OperatorParser, ImportParser, FunctionDefParser, FindLastLineParser, WithParser
3+
from pythonwhat.parsing import FunctionParser, ObjectAccessParser, IfParser, WhileParser, ForParser, OperatorParser, ImportParser, FunctionDefParser, FindLastLineParser, WithParser
44

55
from pythonwhat.Reporter import Reporter
66

@@ -42,6 +42,8 @@ def __init__(
4242
self.solution_function_calls = None
4343
self.used_student_function = None
4444

45+
self.student_object_accesses = None
46+
4547
self.student_imports = None
4648
self.solution_imports = None
4749

@@ -174,6 +176,14 @@ def extract_function_calls(self):
174176
fp.visit(self.solution_tree)
175177
self.solution_function_calls = fp.calls
176178

179+
def extract_object_accesses(self):
180+
self.parse_code()
181+
182+
if (self.student_object_accesses is None):
183+
oap = ObjectAccessParser()
184+
oap.visit(self.student_tree)
185+
self.student_object_accesses = oap.accesses
186+
177187
def extract_imports(self):
178188
self.parse_code()
179189

@@ -252,19 +262,6 @@ def extract_withs(self):
252262
wp.visit(self.solution_tree)
253263
self.solution_withs = wp.withs
254264

255-
def extract_object_accesses(self):
256-
self.parse_code()
257-
258-
if (self.student_object_accesses is None):
259-
oap = ObjectAccessParser()
260-
oap.visit(self.student_tree)
261-
self.student_object_accesses = oap.object_accesses
262-
263-
if (self.solution_object_accesses is None):
264-
oap = ObjectAccessParser()
265-
oap.visit(self.solution_tree)
266-
self.solution_object_accesses = oap.object_accesses
267-
268265
def to_child_state(self, student_subtree, solution_subtree):
269266
"""Dive into nested tree.
270267

pythonwhat/Test.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -282,13 +282,12 @@ def __init__(self, obj, student_env, solution_env, failure_msg):
282282

283283
class BiggerTest(Test):
284284
"""
285-
Check if two objects are equal. Equal means the objects are exactly the same.
286-
This test should only be used with numeric variables (for now).
285+
Check if one object is greater than another. This test should only be used with numeric variables (for now).
287286
288287
Attributes:
289288
failure_msg (str): A string containing the failure message in case the test fails.
290-
obj1 (str): The first object that should be compared with.
291-
obj2 (str): This object is compared to obj1.
289+
obj1 (str): The first object, that should be the greatest
290+
obj2 (str): The second object, that should be smaller
292291
result (bool): True if the test succeed, False if it failed. None if it hasn't been tested yet.
293292
"""
294293

pythonwhat/parsing.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,48 @@ def visit_Name(self, node):
326326
node.id if not node.id in self.imports else self.imports[
327327
node.id])
328328

329+
class ObjectAccessParser(FunctionParser):
330+
"""Find object accesses
331+
332+
A parser which inherits from the FunctionParser. It will walk through the syntax tree and put all
333+
object accesses in a list, which can later be used in the test.
334+
"""
335+
336+
def __init__(self):
337+
super().__init__()
338+
self.accesses = []
339+
340+
def visit_Call(self, node):
341+
for arg in node.args:
342+
self.visit(arg)
343+
344+
for key in node.keywords:
345+
self.visit(key.value)
346+
347+
def visit_List(self, node):
348+
for el in node.elts:
349+
self.visit(el)
350+
351+
def visit_Tuple(self, node):
352+
for el in node.elts:
353+
self.visit(el)
354+
355+
def visit_Attribute(self, node):
356+
if self.current:
357+
self.current = node.attr + "." + self.current
358+
else:
359+
self.current = node.attr
360+
self.visit(node.value)
361+
362+
def visit_Name(self, node):
363+
if self.current:
364+
self.current = node.id + "." + self.current
365+
else:
366+
self.current = node.id
367+
368+
self.accesses.append(self.current)
369+
self.current = ''
370+
329371

330372
class IfParser(Parser):
331373
"""Find if structures.

pythonwhat/test_exercise.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from pythonwhat.test_with import test_with
88
from pythonwhat.test_import import test_import
99
from pythonwhat.test_object import test_object
10+
from pythonwhat.test_object_accessed import test_object_accessed
1011
from pythonwhat.test_correct import test_correct
1112
from pythonwhat.test_if_else import test_if_else
1213
from pythonwhat.set_extra_env import set_extra_env

pythonwhat/test_object_accessed.py

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
from pythonwhat.State import State
22
from pythonwhat.Reporter import Reporter
3-
3+
from pythonwhat.Test import BiggerTest
4+
import pythonwhat.utils
45

56
def test_object_accessed(name,
7+
times=1,
68
not_accessed_msg=None):
79
"""Test if object accessed
810
911
Checks whether an object, or the attribute of an object, are accessed
1012
1113
Args:
1214
name (str): the name of the object that should be accessed; can contain dots (for attributes)
15+
times (int): how often the object specified in name should be accessed.
1316
not_accessed_msg (str): custom feedback message when the object was not accessed.
1417
1518
Examples:
@@ -39,29 +42,16 @@ def test_object_accessed(name,
3942
rep = Reporter.active_reporter
4043
rep.set_tag("fun", "test_object_accessed")
4144

42-
not_accessed_msg = build_strings(not_accessed_msg, name)
45+
if not not_accessed_msg:
46+
add = " at least %s" % pythonwhat.utils.get_times(times) if times > 1 else ""
47+
not_accessed_msg = "Have you accessed `%s`%s?" % (name, add)
4348

4449
state.extract_object_accesses()
50+
student_object_accesses = state.student_object_accesses
51+
student_hits = [c for c in student_object_accesses if name in c]
52+
rep.do_test(BiggerTest(len(student_hits) + 1, times, not_accessed_msg))
4553

46-
student_env = state.student_env
47-
solution_env = state.solution_env
48-
49-
if name not in solution_env:
50-
raise NameError("%r not in solution environment " % name)
51-
52-
rep.do_test(DefinedTest(name, student_env, undefined_msg))
53-
if (rep.failed_test):
54-
return
55-
56-
## CONTINUE HERE
57-
58-
59-
60-
61-
62-
def build_strings(not_called_msg, name):
6354

64-
if not not_called_msg:
65-
incorrect_msg = "Still make meaningful message"
55+
def count_hits (calls, name):
56+
matching = [s for c in calls if name in c]
6657

67-
return(undefined_msg, not_called_msg)

pythonwhat/utils.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,21 @@ def shorten_str(text, to_chars=100):
55
from types import ModuleType
66
import copy
77

8+
def get_ord(num):
9+
nums = {1: "first", 2: "second", 3:"third", 4:"fourth",
10+
5: "fifth", 6: "sixth", 7:"seventh", 8:"eight",
11+
9: "nineth", 10: "tenth"}
12+
if num in nums:
13+
return(nums[num])
14+
else:
15+
return("%dth" % num)
16+
17+
def get_times(num):
18+
nums = {1:"once", 2:"twice"}
19+
if num in nums:
20+
return(nums[num])
21+
else:
22+
return("%d times" % num)
823

924
def copy_env(env, keep_objs=None):
1025
if keep_objs is None:

tests/helper.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
1+
from pythonbackend.Exercise import Exercise
2+
from pythonbackend import utils
3+
14
def get_sct_payload(output):
25
output = [out for out in output if out['type'] == 'sct']
36
if (len(output) > 0):
47
return(output[0]['payload'])
58
else:
69
return(None)
7-
10+
11+
def run(data):
12+
exercise = Exercise(data)
13+
exercise.runInit()
14+
output = exercise.runSubmit(data)
15+
return(get_sct_payload(output))

tests/test_test_object_accessed.py

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,50 +4,70 @@
44
from os.path import exists
55
from unittest.mock import patch
66

7-
from pythonbackend.Exercise import Exercise
8-
from pythonbackend import utils
9-
107
import helper
118

129
class TestTestObjectAccessed(unittest.TestCase):
1310

1411
def setUp(self):
1512
self.data = {
13+
"DC_PEC": '',
1614
"DC_CODE": '''
1715
import numpy as np
1816
arr = np.array([1, 2, 3])
1917
x = arr.shape
18+
print(arr.data)
2019
''',
2120
"DC_SOLUTION": '''
22-
import numpy as np
23-
arr = np.array([1, 2, 3])
24-
x = arr.shape
25-
t = arr.dtype
21+
# not used
2622
'''
2723
}
2824

29-
def test_standardTestPass(self):
25+
def test_objectOnly(self):
3026
self.data["DC_SCT"] = 'test_object_accessed("arr")'
31-
self.exercise = Exercise(self.data)
32-
self.exercise.runInit()
33-
output = self.exercise.runSubmit(self.data)
34-
sct_payload = helper.get_sct_payload(output)
27+
sct_payload = helper.run(self.data)
28+
self.assertEqual(sct_payload['correct'], True)
29+
30+
self.data["DC_SCT"] = 'test_object_accessed("arr", times=2)'
31+
sct_payload = helper.run(self.data)
3532
self.assertEqual(sct_payload['correct'], True)
3633

34+
self.data["DC_SCT"] = 'test_object_accessed("arr", times=3)'
35+
sct_payload = helper.run(self.data)
36+
self.assertEqual(sct_payload['correct'], False)
37+
self.assertEqual(sct_payload['message'], "Have you accessed <code>arr</code> at least 3 times?")
38+
39+
self.data["DC_SCT"] = 'test_object_accessed("arr", times=3, not_accessed_msg="silly")'
40+
sct_payload = helper.run(self.data)
41+
self.assertEqual(sct_payload['correct'], False)
42+
self.assertEqual(sct_payload['message'], "silly")
43+
44+
45+
def test_objectAndAttribute(self):
46+
3747
self.data["DC_SCT"] = 'test_object_accessed("arr.shape")'
38-
self.exercise = Exercise(self.data)
39-
self.exercise.runInit()
40-
output = self.exercise.runSubmit(self.data)
41-
sct_payload = helper.get_sct_payload(output)
48+
sct_payload = helper.run(self.data)
4249
self.assertEqual(sct_payload['correct'], True)
50+
51+
self.data["DC_SCT"] = 'test_object_accessed("arr.shape", times=2)'
52+
sct_payload = helper.run(self.data)
53+
self.assertEqual(sct_payload['correct'], False)
54+
self.assertEqual(sct_payload['message'], "Have you accessed <code>arr.shape</code> at least twice?")
55+
56+
self.data["DC_SCT"] = 'test_object_accessed("arr.shape", times=2, not_accessed_msg="silly")'
57+
sct_payload = helper.run(self.data)
58+
self.assertEqual(sct_payload['correct'], False)
59+
self.assertEqual(sct_payload['message'], "silly")
4360

4461
self.data["DC_SCT"] = 'test_object_accessed("arr.dtype")'
45-
self.exercise = Exercise(self.data)
46-
self.exercise.runInit()
47-
output = self.exercise.runSubmit(self.data)
48-
sct_payload = helper.get_sct_payload(output)
62+
sct_payload = helper.run(self.data)
4963
self.assertEqual(sct_payload['correct'], False)
50-
64+
self.assertEqual(sct_payload['message'], "Have you accessed <code>arr.dtype</code>?")
65+
66+
self.data["DC_SCT"] = 'test_object_accessed("arr.dtype", not_accessed_msg="silly")'
67+
sct_payload = helper.run(self.data)
68+
self.assertEqual(sct_payload['correct'], False)
69+
self.assertEqual(sct_payload['message'], "silly")
70+
5171

5272
if __name__ == "__main__":
5373
unittest.main()

tests/test_utils.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import os
2+
import unittest
3+
4+
from os.path import exists
5+
from unittest.mock import patch
6+
from pythonwhat import utils
7+
8+
class TestUtils(unittest.TestCase):
9+
10+
def test_get_ord(self):
11+
self.assertEqual(utils.get_ord(1), "first")
12+
self.assertEqual(utils.get_ord(2), "second")
13+
self.assertEqual(utils.get_ord(3), "third")
14+
self.assertEqual(utils.get_ord(11), "11th")
15+
16+
def test_get_times(self):
17+
self.assertEqual(utils.get_times(1), "once")
18+
self.assertEqual(utils.get_times(2), "twice")
19+
self.assertEqual(utils.get_times(3), "3 times")
20+
self.assertEqual(utils.get_times(11), "11 times")
21+
22+
if __name__ == "__main__":
23+
unittest.main()

0 commit comments

Comments
 (0)