Skip to content

Commit aa44e0d

Browse files
authored
Merge pull request #384 from datacamp/jh/improve-run
Improve run
2 parents b2bf6df + b094051 commit aa44e0d

4 files changed

Lines changed: 126 additions & 12 deletions

File tree

pythonwhat/local.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -205,16 +205,14 @@ def run_exercise(pec, sol_code, stu_code, sol_wd=None, stu_wd=None, **kwargs):
205205
# e.g. `python -m project.run
206206
# allow setting env vars? e.g. PYTHONPATH, could help running more complex setup
207207
# allow prepending code? set_env? e.g. (automatically) setting __file__?
208-
def run(state, relative_working_dir="", solution_dir="solution"):
208+
def run(state, relative_working_dir=None, solution_dir="solution"):
209209
"""Run the focused student and solution code in the specified location
210210
211211
This function can be used after ``check_file`` to execute student and solution code.
212-
The arguments allow setting the correct context for execution.
213-
The ``solution_dir`` allows setting a different root of the solution context
214-
so solution side effects don't conflict with those of the student.
212+
The arguments allow configuring the correct context for execution.
215213
216214
SCT functions chained after this one that execute pieces of code (custom expressions or the focused part of a file)
217-
execute in the same location as the file.
215+
execute in the same student and solution locations as the file.
218216
219217
.. note::
220218
@@ -235,6 +233,14 @@ def run(state, relative_working_dir="", solution_dir="solution"):
235233
that sets the root of the solution context, relative to that of the student execution context
236234
state (State): state as passed by the SCT chain. Don't specify this explicitly.
237235
236+
If ``relative_working_dir`` is not set, it will be the directory the file was loaded from by ``check_file``
237+
and fall back to the root of the student execution context (the working directory pythonwhat runs in).
238+
239+
The ``solution_dir`` helps to prevent solution side effects from conflicting with those of the student.
240+
If the set or derived value of ``relative_working_dir`` is an absolute path,
241+
``relative_working_dir`` will not be used to form the solution execution working directory:
242+
the solution code will be executed in the root of the solution execution context.
243+
238244
:Example:
239245
240246
Suppose the student and solution have a file ``script.py`` in ``/home/repl/``::
@@ -254,10 +260,20 @@ def run(state, relative_working_dir="", solution_dir="solution"):
254260
has_printout(0)
255261
)
256262
"""
257-
# todo: configure these arguments automatically based on check_file info?
258-
# once that is implemented, look into executing the file itself
263+
# todo:
264+
# look into executing the file itself
259265
# and keeping the process alive to extract values
260-
sol_wd = Path(os.getcwd(), solution_dir, relative_working_dir)
266+
if relative_working_dir is None:
267+
if getattr(state, "path", False):
268+
relative_working_dir = state.path.parent
269+
else:
270+
relative_working_dir = ""
271+
272+
if not os.path.isabs(str(relative_working_dir)):
273+
sol_wd = Path(os.getcwd(), solution_dir, relative_working_dir)
274+
else:
275+
sol_wd = Path(os.getcwd(), solution_dir)
276+
261277
os.makedirs(str(sol_wd), exist_ok=True)
262278
stu_wd = Path(os.getcwd(), relative_working_dir)
263279
sol_process, stu_process, raw_stu_output, error = run_exercise(

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# pythonwhat deps
2-
protowhat~=1.9.0
2+
protowhat~=1.10.0
33
asttokens~=1.1.10
44
dill~=0.2.7.1
55
markdown2~=2.3.7

tests/helper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def wrapper(*args, **kwargs):
4747
def in_temp_dir():
4848
with tempfile.TemporaryDirectory() as d:
4949
with ChDir(d):
50-
yield
50+
yield d
5151

5252

5353
def run(data, run_code=True):

tests/test_local.py

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
import os
2+
from pathlib import Path
3+
14
import pytest
25

6+
from pythonwhat.local import ChDir
37
from pythonwhat.test_exercise import setup_state
48
from tests.helper import verify_sct, in_temp_dir
59

@@ -50,7 +54,7 @@ def test_running_code_isolation_run(sol_code, stu_code):
5054
asset
5155
),
5256
)
53-
* 2,
57+
* 2
5458
],
5559
)
5660
def test_urlretrieve_in_process(sol_code, stu_code):
@@ -77,7 +81,7 @@ def test_urlretrieve_in_process(sol_code, stu_code):
7781
asset
7882
),
7983
)
80-
* 2,
84+
* 2
8185
],
8286
)
8387
def test_urlopen_in_process(sol_code, stu_code):
@@ -89,3 +93,97 @@ def test_urlopen_in_process(sol_code, stu_code):
8993

9094
with verify_sct(True):
9195
chain.run()
96+
97+
98+
def write_file(path, name, content):
99+
os.makedirs(path)
100+
with ChDir(path):
101+
with open(name, "w") as f:
102+
f.write(content)
103+
104+
105+
def test_run_relative_working_dir():
106+
stu_code = 'from pathlib import Path; c = Path("c").read_text(encoding="utf-8")'
107+
sol_code = """c = 'c= Path("c").read_text(encoding="utf-8")'"""
108+
109+
file_dir = "a/b"
110+
file_path = "a/b/c"
111+
112+
with in_temp_dir():
113+
114+
write_file(file_dir, "c", stu_code)
115+
116+
chain = setup_state("", "", pec="")
117+
118+
child = chain.check_file(file_path, solution_code=sol_code)
119+
120+
child.run(file_dir).check_object("c")
121+
child.run().check_object("c")
122+
123+
124+
def test_run_solution_dir():
125+
code = 'from pathlib import Path; c = Path("c").read_text(encoding="utf-8")'
126+
127+
file_dir = "a/b"
128+
file_path = "a/b/c"
129+
solution_location = "solution"
130+
131+
with in_temp_dir():
132+
write_file(file_dir, "c", code)
133+
134+
os.makedirs(solution_location)
135+
with ChDir(solution_location):
136+
write_file(file_dir, "c", code)
137+
138+
chain = setup_state("", "", pec="")
139+
140+
child = chain.check_file(file_path, solution_code=code)
141+
142+
child.run(file_dir).check_object("c")
143+
child.run().check_object("c")
144+
145+
146+
def test_run_with_absolute_dir():
147+
code = 'from pathlib import Path; c = Path("c").read_text(encoding="utf-8")'
148+
149+
file_dir = "a/b"
150+
solution_location = "solution"
151+
152+
with in_temp_dir():
153+
os.makedirs(file_dir)
154+
with ChDir(file_dir):
155+
abs_dir = os.path.abspath(".")
156+
abs_file_path = Path(abs_dir, "c")
157+
with open("c", "w") as f:
158+
f.write(code)
159+
160+
write_file(solution_location, "c", code)
161+
162+
chain = setup_state("", "", pec="")
163+
164+
child = chain.check_file(abs_file_path, solution_code=code)
165+
166+
child.run(abs_dir).check_object("c")
167+
child.run().check_object("c")
168+
169+
170+
def test_run_custom_solution_dir():
171+
code = 'from pathlib import Path; c = Path("c").read_text(encoding="utf-8")'
172+
173+
file_dir = "a/b"
174+
file_path = "a/b/c"
175+
custom_solution_location = "custom/solution/folder"
176+
177+
with in_temp_dir():
178+
write_file(file_dir, "c", code)
179+
180+
os.makedirs(custom_solution_location)
181+
with ChDir(custom_solution_location):
182+
write_file(file_dir, "c", code)
183+
184+
chain = setup_state("", "", pec="")
185+
186+
child = chain.check_file(file_path, solution_code=code)
187+
188+
child.run(file_dir, solution_dir=custom_solution_location).check_object("c")
189+
child.run(solution_dir=custom_solution_location).check_object("c")

0 commit comments

Comments
 (0)