Skip to content

Commit f4ff2d0

Browse files
committed
added coverage & fix modal stdout stream errors
1 parent 0b549d9 commit f4ff2d0

File tree

3 files changed

+39
-64
lines changed

3 files changed

+39
-64
lines changed

commit0/harness/execution_context.py

Lines changed: 29 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,6 @@
3232
)
3333

3434

35-
def read_stream(stream: modal.io_streams.StreamReader) -> str:
36-
"""Read stream"""
37-
strings = []
38-
for line in stream:
39-
strings.append(line)
40-
return "\n".join(strings)
41-
42-
4335
class ExecutionBackend(StrEnum):
4436
LOCAL = auto()
4537
MODAL = auto()
@@ -54,6 +46,7 @@ def __init__(
5446
num_cpus: int,
5547
log_dir: Path,
5648
files_to_copy: Optional[Files] = None,
49+
files_to_collect: Optional[Files] = None,
5750
):
5851
"""Create the remote execution context
5952
@@ -65,25 +58,13 @@ def __init__(
6558
self.timeout = timeout
6659
self.num_cpus = num_cpus
6760
self.log_dir = log_dir
61+
self.files_to_collect = files_to_collect
6862

6963
@abstractmethod
7064
def exec_run_with_timeout(self, command: str) -> tuple[str, bool, float]:
7165
"""Execute a test command"""
7266
raise NotImplementedError
7367

74-
def write_test_output(self, test_output: str, timed_out: bool) -> None:
75-
"""Write test output"""
76-
test_output_path = self.log_dir / "test_output.txt"
77-
with open(test_output_path, "w") as f:
78-
f.write(test_output)
79-
if timed_out:
80-
f.write(f"\n\nTimeout error: {self.timeout} seconds exceeded.")
81-
raise EvaluationError(
82-
self.spec.repo,
83-
f"Test timed out after {self.timeout} seconds.",
84-
self.logger,
85-
)
86-
8768
def __enter__(self):
8869
return self
8970

@@ -106,8 +87,9 @@ def __init__(
10687
num_cpus: int,
10788
log_dir: Path,
10889
files_to_copy: Optional[Files] = None,
90+
files_to_collect: Optional[Files] = None,
10991
):
110-
super().__init__(spec, logger, timeout, num_cpus, log_dir)
92+
super().__init__(spec, logger, timeout, num_cpus, log_dir, files_to_copy=files_to_copy, files_to_collect=files_to_collect)
11193

11294
self.client = docker.from_env()
11395
self.container = create_container(
@@ -126,17 +108,18 @@ def exec_run_with_timeout(self, command: str) -> tuple[str, bool, float]:
126108
"""Exec"""
127109
output = exec_run_with_timeout(self.container, command, self.timeout)
128110

129-
# copy back report.json if there is any
130-
report_file = Path(self.spec.repo_directory) / "report.json"
131-
# Run the test command inside the container to check if the file exists
132-
exit_code, test_output = self.container.exec_run(
133-
f"test -e {report_file}", demux=True
134-
)
135-
# Check the exit code of the command
136-
if exit_code == 0:
137-
copy_from_container(
138-
self.container, report_file, self.log_dir / "report.json"
111+
for fname in self.files_to_collect:
112+
# copy back report.json if there is any
113+
file = Path(self.spec.repo_directory) / fname
114+
# Run the test command inside the container to check if the file exists
115+
exit_code, test_output = self.container.exec_run(
116+
f"test -e {file}", demux=True
139117
)
118+
# Check the exit code of the command
119+
if exit_code == 0:
120+
copy_from_container(
121+
self.container, file, self.log_dir / fname
122+
)
140123
return output
141124

142125
def __exit__(
@@ -158,8 +141,9 @@ def __init__(
158141
num_cpus: int,
159142
log_dir: Path,
160143
files_to_copy: Optional[Files] = None,
144+
files_to_collect: Optional[Files] = None,
161145
):
162-
super().__init__(spec, logger, timeout, num_cpus, log_dir)
146+
super().__init__(spec, logger, timeout, num_cpus, log_dir, files_to_copy=files_to_copy, files_to_collect=files_to_collect)
163147

164148
self.app = modal.App()
165149

@@ -176,13 +160,17 @@ def exec_run_with_timeout(self, command: str) -> tuple[str, bool, float]:
176160
"""Execute command on modal sandbox"""
177161
start_time = time.time()
178162
with modal.Volume.ephemeral() as vol:
179-
# copy back report.json if there is any
180-
report_file = Path(self.spec.repo_directory) / "report.json"
163+
cp_cmd = ""
164+
for fname in self.files_to_collect:
165+
remote_file = Path(self.spec.repo_directory) / fname
166+
curr_cp_cmd = f" && cp {str(remote_file)} /vol/{fname} 2>/dev/null"
167+
cp_cmd += curr_cp_cmd
181168

169+
command += cp_cmd
182170
self.sandbox = modal.Sandbox.create(
183171
"bash",
184172
"-c",
185-
f"{command} && cp {str(report_file)} /vol/report.json",
173+
command,
186174
image=self.image,
187175
cpu=self.num_cpus,
188176
timeout=self.timeout,
@@ -191,26 +179,21 @@ def exec_run_with_timeout(self, command: str) -> tuple[str, bool, float]:
191179
)
192180
self.sandbox.wait()
193181

194-
# stdout has been redirected to stderr
195-
stdout = read_stream(self.sandbox.stderr)
196-
197182
return_code = self.sandbox.returncode
198183
# https://github.com/modal-labs/modal-client/blob/d577b2916b5c3bf4ebbcb58fadced84d85e1cf8c/modal/sandbox.py#L413
199184
if return_code == 124:
200185
timed_out = True
201186
else:
202187
timed_out = False
203188

204-
# copy over report.json from mount
205-
with (self.log_dir / "report.json").open("wb") as f:
206-
for data in vol.read_file("report.json"):
207-
f.write(data)
189+
for fname in self.files_to_collect:
190+
with (self.log_dir / fname).open("wb") as f:
191+
for data in vol.read_file(fname):
192+
f.write(data)
208193

209194
self.sandbox.terminate()
210-
211195
end_time = time.time()
212-
213-
return stdout, timed_out, end_time - start_time
196+
return self.sandbox.stderr.read(), timed_out, end_time - start_time
214197

215198
def __exit__(
216199
self,

commit0/harness/run_pytest_ids.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -114,28 +114,22 @@ def main(
114114
eval_script={"src": eval_file, "dest": Path("/eval.sh")},
115115
patch={"src": patch_file, "dest": Path("/patch.diff")},
116116
)
117+
files_to_collect = ["report.json", "coverage.json", "pytest_exit_code.txt", "test_output.txt"]
117118

118119
try:
119120
with execution_context(
120-
spec, logger, timeout, num_cpus, log_dir, files_to_copy
121+
spec, logger, timeout, num_cpus, log_dir, files_to_copy, files_to_collect
121122
) as context:
122123
output, timed_out, total_runtime = context.exec_run_with_timeout(
123124
"/bin/bash /eval.sh"
124125
)
125-
logger.info(output)
126-
test_output = extract_test_output(
127-
output, "--json-report --json-report-file=report.json"
128-
)
129-
context.write_test_output(test_output, timed_out)
130-
if stdout:
131-
print(test_output)
132-
pytest_exit_code = extract_test_output(output, "echo ")
133-
try:
134-
pytest_exit_code = int(pytest_exit_code)
135-
except Exception:
136-
raise Exception(
137-
f"Fail to convert pytest_exit_code {pytest_exit_code} into an integer."
126+
if timed_out:
127+
raise EvaluationError(
128+
self.spec.repo,
129+
f"Test timed out after {timeout} seconds.",
130+
self.logger,
138131
)
132+
pytest_exit_code = Path(log_dir / "pytest_exit_code.txt").read_text().strip()
139133
sys.exit(pytest_exit_code)
140134
except EvaluationError as e:
141135
error_msg = (

commit0/harness/spec.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,11 @@ def make_eval_script_list(instance: RepoInstance, repo_directory: str) -> list[s
153153
f"git reset --hard {instance['base_commit']}",
154154
"git apply --allow-empty -v /patch.diff",
155155
"git status",
156-
f"{instance['test']['test_cmd']} --json-report --json-report-file=report.json --continue-on-collection-errors {{test_ids}}",
157-
"echo $?",
156+
f"{instance['test']['test_cmd']} --json-report --json-report-file=report.json --continue-on-collection-errors --cov=. --cov-branch --cov-report json {{test_ids}} > test_output.txt 2>&1",
157+
"echo $? > pytest_exit_code.txt",
158158
f"git reset --hard {instance['base_commit']}",
159159
"git status",
160160
]
161-
for i in range(len(eval_script_list)):
162-
eval_script_list[i] = f"{eval_script_list[i]} 1>&2"
163161
return eval_script_list
164162

165163

0 commit comments

Comments
 (0)