-
Notifications
You must be signed in to change notification settings - Fork 0
/
checker.py
executable file
·191 lines (126 loc) · 5.41 KB
/
checker.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
"""Runner for assignment sanity checkers, including PyTA.
Copyright (c) 2021 Anya Tafliovich.
"""
import sys
from typing import Any, Tuple
ERROR_MSG = 'The call {}({}) caused an error: {}'
TYPE_ERROR_MSG = '{} should return a {}, but returned {}.'
def ensure_no_io(modulename: str) -> None:
"""Mock built-in functions input and print, so that they raise
exceptions.
"""
test_module = sys.modules[modulename]
setattr(test_module, "input", _mock_disallow("input"))
setattr(test_module, "print", _mock_disallow("print"))
def run_pyta(filename: str, config_file: str) -> None:
"""Run PYTA with configuration config_file on the file named filename.
"""
sys.path.insert(0, 'pyta')
import python_ta
python_ta.check_all(filename, config=config_file)
def type_check_simple(func: callable, args: list,
expected: type) -> Tuple[bool, Any]:
"""Check if func(args) returns a result of type expected.
Return (True, result-of-call) if the check succeeds.
Return (False, error-or-failure-message) if anything goes wrong.
"""
try:
returned = func(*args)
except Exception as exn:
return (False, error_message(func, args, exn))
if isinstance(returned, expected):
return (True, returned)
return (False,
type_error_message(func.__name__, expected.__name__, returned))
def type_check_full(func: callable, args: list,
checker_function: callable) -> Tuple[bool, Any]:
"""Run checker_function on func(args).
Pre: checker_function is also a type-checker (i.e. returns tuple
in the same format).
Return (True, result-of-call-func-args) if the check succeeds.
Return (False, error-or-failure-message) if anything goes wrong.
"""
try:
returned = func(*args)
except Exception as exn:
return (False, error_message(func, args, exn))
return checker_function(returned)
def type_error_message(func: str, expected: str, got: object) -> str:
"""Return an error message for function func returning got, where the
correct return type is expected.
"""
return TYPE_ERROR_MSG.format(func, expected, got)
def error_message(func: callable, args: list,
error: Exception) -> str:
"""Return an error message: func(args) raised an error."""
return ERROR_MSG.format(func.__name__, ','.join(map(str, args)),
error)
def returns_list_of(func: callable, args: list, typ: type) -> Tuple[bool, Any]:
"""Check if func(args) returns a list of elements of type typ.
Return (True, result-of-call) if the check succeeds.
Return (False, error-or-failure-message) if anything goes wrong.
"""
result = type_check_simple(func, args, list)
if not result[0]:
return (False, result[1])
msg = type_error_message(func.__name__, 'list of {}s'.format(typ.__name__),
result[1])
for item in result[1]:
if not isinstance(item, typ):
return (False, msg)
return (True, result[1])
def returns_dict_of(func: callable, args: list, key_type: type,
value_type: type) -> Tuple[bool, Any]:
"""Check if func(args) returns a dict that maps objects of type
key_type to obects of type value_type.
Return (True, result-of-call) if the check succeeds.
Return (False, error-or-failure-message) if anything goes wrong.
"""
result = type_check_simple(func, args, dict)
if not result[0]:
return (False, result[1])
msg = type_error_message(
func.__name__, 'dict of {}s to {}s'.format(key_type.__name__,
value_type.__name__),
result[1])
for key, value in result[1].items():
if not isinstance(key, key_type) or not isinstance(value, value_type):
return (False, msg)
return (True, result[1])
def returns_dict_keys_from(func: callable, args: list, keyset: set):
"""Check if func(args) returns a dict whose keys are all from keyset.
Return (True, result-of-call) if the check succeeds.
Return (False, error-or-failure-message) if anything goes wrong.
"""
result = type_check_simple(func, args, dict)
if not result[0]:
return (False, result[1])
msg = error_message(func, args, 'Incorrect key: {}. Allowed keys are: {}')
for key in result[1]:
if key not in keyset:
return (False, msg.format(key, keyset))
return (True, result[1])
def returns_dict_keys(func: callable, args: list, keyset: set):
"""Check if func(args) returns a dict whose keys are exactly those
from keyset.
Return (True, result-of-call) if the check succeeds.
Return (False, error-or-failure-message) if anything goes wrong.
"""
result = type_check_simple(func, args, dict)
if not result[0]:
return (False, result[1])
actual_keyset = set(result[1].keys())
msg = error_message(func, args,
'Incorrect keys: {}. Keys must be: {}'.format(
actual_keyset, keyset))
if actual_keyset != keyset:
return (False, msg)
return (True, result[1])
def _mock_disallow(func_name: str):
"""Raise an Exception saying that use of function func_name is not
allowed.
"""
def mocker(*args):
raise Exception(
"The use of function {} is not allowed.".format(func_name))
return mocker