Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 97
Expand file tree
/
Copy pathharness.py
More file actions
Latest commit
191 lines (151 loc) · 5.61 KB
/
Copy pathharness.py
File metadata and controls
191 lines (151 loc) · 5.61 KB
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
"""
File: harness.py
----------------
The lab harness for CS 41 that allows the user to interact with the testable
functions in their lab assignment.
History
-------
8/9/2020 -- Created by @psarin
8/12/2020 -- Looks like we're using cmd!
"""
importcmd# For the terminal interface
importinspect# For grabbing information about student functions
importos# For the files in the current dir
fromtypingimport*# For enforcing and displaying type information
importtraceback# For displaying errors to students
defget_testable_functions():
"""
Returns a list of the testable functions by extracting them from the Python
files in the current directory.
"""
# Check the files that aren't the current file.
curr_file=os.path.basename(__file__)
files_to_check= [
fforfinos.listdir() iff.endswith('.py') andf!=curr_file
]
# Collect a list of functions to test
output= []
forfileinfiles_to_check:
module_name=file[:-3]
module=__import__(module_name)
try:
output+=module.TESTABLE
exceptAttributeError:
# No testable functions in the file
pass
returnoutput
classHarness(cmd.Cmd):
prompt='🦄 > '
def__init__(self, choices, *args, **kwargs):
super().__init__(*args, **kwargs)
self.choices=choices
self.intro= (
"Welcome to the CS 41 Lab Harness! Type ? or help to get a list of "
"commands or type \na number to execute tests on that function.\n\n"
f"Functions\n=========\n{self._make_choices_lst(choices)}\n"
)
@staticmethod
def_get_signature(fn):
"""
Returns the signature of fn as a string, where fn is a callable object.
"""
returnf"{fn.__name__}{inspect.signature(fn)}"
@staticmethod
def_make_choices_lst(choices):
"""
Join function choices togeher into an indexed list
"""
return'\n'.join([
"{}: {}".format(i+1, Harness._get_signature(choice))
fori, choiceinenumerate(choices)
])
defdefault(self, arg):
"""
The default interpretation of arg.
"""
try:
chosen_i=int(arg) -1
chosen_f=self.choices[chosen_i]
exceptValueError:
print("Please enter a valid command.")
return
exceptIndexError:
print(f"Please enter a number between 1 and {len(self.choices)}.")
return
self.test(chosen_f)
@staticmethod
def_get_arg(arg_name: str, arg_type=None):
"""
Prompts the user for an arg_name of type arg_type and returns the result
once they enter valid Python code.
"""
type_hint=''
type_comparison=None
# Define the type hint and find the highest object we can compare to
ifarg_type:
type_hint=f' ({arg_type})'ifarg_typeelse''
try:
type_comparison=arg_type.__origin__
exceptAttributeError:
type_comparison=arg_type
# Combine those to the prompt
prompt=f"{arg_name}{type_hint.replace('typing.', '')}? "
# Prompt until the user inputs a good value
whileTrue:
try:
res=eval(input(prompt))
exceptExceptionase:
print(f"Your input raised an error: {e}. Try again.")
continue
ifnotisinstance(res, type_comparison):
print("You didn't enter an object with the correct type.")
continue
break
returnres
@staticmethod
deftest(fn):
"""
Initiates tests on the function fn.
"""
print(f"Testing {Harness._get_signature(fn)}\nPlease enter valid "
"Python code as inputs for each of the arguments.")
argspec=inspect.getfullargspec(fn)
annotations=argspec.annotations
# Collect information about what to pass to the funciton
args= []
forarginargspec.args:
args.append(Harness._get_arg(arg, annotations.get(arg)))
varargs= ()
ifargspec.varargs:
varargs=Harness._get_arg(argspec.varargs, tuple)
kwonlyargs= {}
forarginargspec.kwonlyargs:
kwonlyargs[arg] =Harness._get_arg(arg, annotations.get(arg))
varkw= {}
ifargspec.varkw:
varkw=Harness._get_arg(argspec.varkw, dict)
# Collect the args into a string
str_arg_lst= [str(a) forainargs+list(varargs)] \
+ [f"{k}={repr(v)}"fork, vinkwonlyargs.items()] \
+ [f"{k}={repr(v)}"fork, vinvarkw.items()]
str_rep=', '.join(str_arg_lst)
# Run the function
print()
print(f"Calling {fn.__name__}({str_rep})...")
try:
retval=fn(*args, *varargs, **kwonlyargs, **varkw)
exceptExceptionase:
print(f"The function raised an error: {e}.")
print(traceback.format_exc(chain=False))
else:
print(f"=> {repr(retval)}")
print()
defdo_exit(self, arg):
'Closes the CS 41 lab harness.'
print("Good bye! Have a lovely, unicorn-filled day.")
returnTrue
defdo_list(self, arg):
'Prints a list of functions that are available for testing.'
print(f"Functions\n=========\n{self._make_choices_lst(self.choices)}\n")
if__name__=='__main__':
Harness(get_testable_functions()).cmdloop()