23
23
"""
24
24
import contextlib
25
25
import os
26
- import shutil
27
26
import statistics
28
27
from pathlib import Path
29
- from tempfile import TemporaryDirectory
30
28
from typing import Callable , List , Optional , TypeVar
31
29
32
30
import scipy
33
31
34
32
from framework import utils
35
- from framework .defs import FC_WORKSPACE_DIR
36
33
from framework .microvm import Microvm
37
34
from framework .utils import CommandReturn
38
35
from framework .with_filelock import with_filelock
39
- from host_tools .cargo_build import get_firecracker_binaries
36
+ from host_tools .cargo_build import get_binary , get_firecracker_binaries
40
37
41
38
# Locally, this will always compare against main, even if we try to merge into, say, a feature branch.
42
39
# We might want to do a more sophisticated way to determine a "parent" branch here.
@@ -85,25 +82,19 @@ def git_ab_test(
85
82
(alternatively, your comparator can perform any required assertions and not return anything).
86
83
"""
87
84
88
- # We can't just checkout random branches in the current working directory. Locally, this might not work because of
89
- # uncommitted changes. In the CI this will not work because multiple tests will run in parallel, and thus switching
90
- # branches will cause random failures in other tests.
91
- with temporary_checkout (a_revision ) as a_tmp :
92
- result_a = test_runner (a_tmp , True )
85
+ dir_a = git_clone (Path ("build" ) / a_revision , a_revision )
86
+ result_a = test_runner (dir_a , True )
93
87
94
- if b_revision :
95
- with temporary_checkout (b_revision ) as b_tmp :
96
- result_b = test_runner (b_tmp , False )
97
- # Have to call comparator here to make sure both temporary directories exist (as the comparator
98
- # might rely on some files that were created during test running, see the benchmark test)
99
- comparison = comparator (result_a , result_b )
100
- else :
101
- # By default, pytest execution happens inside the `tests` subdirectory. Pass the repository root, as
102
- # documented.
103
- result_b = test_runner (Path .cwd ().parent , False )
104
- comparison = comparator (result_a , result_b )
88
+ if b_revision :
89
+ dir_b = git_clone (Path ("build" ) / b_revision , b_revision )
90
+ else :
91
+ # By default, pytest execution happens inside the `tests` subdirectory. Pass the repository root, as
92
+ # documented.
93
+ dir_b = Path .cwd ().parent
94
+ result_b = test_runner (dir_b , False )
105
95
106
- return result_a , result_b , comparison
96
+ comparison = comparator (result_a , result_b )
97
+ return result_a , result_b , comparison
107
98
108
99
109
100
def is_pr () -> bool :
@@ -162,45 +153,6 @@ def set_did_not_grow_comparator(
162
153
)
163
154
164
155
165
- def git_ab_test_with_binaries (
166
- test_runner : Callable [[Path , Path ], T ],
167
- comparator : Callable [[T , T ], U ] = default_comparator ,
168
- * ,
169
- a_revision : str = DEFAULT_A_REVISION ,
170
- b_revision : Optional [str ] = None ,
171
- ) -> (T , T , U ):
172
- """Similar to `git_ab_test`, with the only difference being that this function compiles firecracker at the specified
173
- revisions and only passes the firecracker binaries to the test_runner. Maintains a cache of previously compiled
174
- revisions, to prevent excessive recompilation across different tests of the same revision
175
- """
176
-
177
- @with_filelock
178
- def grab_binaries (checkout : Path ):
179
- with chdir (checkout ):
180
- revision = utils .run_cmd ("git rev-parse HEAD" ).stdout .strip ()
181
-
182
- revision_store = FC_WORKSPACE_DIR / "build" / revision
183
- if not revision_store .exists ():
184
- with chdir (checkout ):
185
- firecracker , jailer = get_firecracker_binaries (workspace_dir = checkout )
186
-
187
- revision_store .mkdir (parents = True , exist_ok = True )
188
- shutil .copy (firecracker , revision_store / "firecracker" )
189
- shutil .copy (jailer , revision_store / "jailer" )
190
-
191
- return (
192
- revision_store / "firecracker" ,
193
- revision_store / "jailer" ,
194
- )
195
-
196
- return git_ab_test (
197
- lambda checkout , _is_a : test_runner (* grab_binaries (checkout )),
198
- comparator ,
199
- a_revision = a_revision ,
200
- b_revision = b_revision ,
201
- )
202
-
203
-
204
156
def git_ab_test_guest_command (
205
157
microvm_factory : Callable [[Path , Path ], Microvm ],
206
158
command : str ,
@@ -212,11 +164,16 @@ def git_ab_test_guest_command(
212
164
"""The same as git_ab_test_command, but via SSH. The closure argument should setup a microvm using the passed
213
165
paths to firecracker and jailer binaries."""
214
166
215
- def test_runner (firecracker , jailer ):
167
+ def test_runner (workspace_dir , _is_a : bool ):
168
+ utils .run_cmd ("./tools/release.sh --profile release" , cwd = workspace_dir )
169
+ bin_dir = get_binary (
170
+ "firecracker" , workspace_dir = workspace_dir
171
+ ).parent .resolve ()
172
+ firecracker , jailer = bin_dir / "firecracker" , bin_dir / "jailer"
216
173
microvm = microvm_factory (firecracker , jailer )
217
174
return microvm .ssh .run (command )
218
175
219
- (_ , old_out , old_err ), (_ , new_out , new_err ), the_same = git_ab_test_with_binaries (
176
+ (_ , old_out , old_err ), (_ , new_out , new_err ), the_same = git_ab_test (
220
177
test_runner , comparator , a_revision = a_revision , b_revision = b_revision
221
178
)
222
179
@@ -270,35 +227,25 @@ def check_regression(
270
227
)
271
228
272
229
273
- @contextlib .contextmanager
274
- def temporary_checkout (revision : str ):
275
- """
276
- Context manager that checks out firecracker in a temporary directory and `chdir`s into it
230
+ @with_filelock
231
+ def git_clone (clone_path , commitish ):
232
+ """Clone the repository at `commit`.
277
233
278
- Will change back to whatever was the current directory when the context manager was entered, even if exceptions
279
- happen along the way.
234
+ :return: the working copy directory.
280
235
"""
281
- with TemporaryDirectory () as tmp_dir :
282
- basename = Path (tmp_dir ).name
236
+ if not clone_path .exists ():
283
237
ret , _ , _ = utils .run_cmd (
284
- f"git cat-file -t { revision } " , ignore_return_code = True
238
+ f"git cat-file -t { commitish } " , ignore_return_code = True
285
239
)
286
240
if ret != 0 :
287
- # git didn't recognize this object, so maybe it is a branch; qualify it
288
- revision = f"origin/{ revision } "
241
+ # git didn't recognize this object; qualify it if it is a branch
242
+ commitish = f"origin/{ commitish } "
289
243
# make a temp branch for that commit so we can directly check it out
290
- utils .run_cmd (f"git branch { basename } { revision } " )
291
- # `git clone` can take a path instead of an URL, which causes it to create a copy of the
292
- # repository at the given path. However, that path needs to point to the root of a repository,
293
- # it cannot be some arbitrary subdirectory. Therefore:
244
+ utils .run_cmd (f"git branch { clone_path } { commitish } " )
294
245
_ , git_root , _ = utils .run_cmd ("git rev-parse --show-toplevel" )
295
246
# split off the '\n' at the end of the stdout
296
- utils .run_cmd (f"git clone -b { basename } { git_root .strip ()} { tmp_dir } " )
297
- yield Path (tmp_dir )
298
-
299
- # If we compiled firecracker inside the checkout, python's recursive shutil.rmdir will
300
- # run incredibly long. Thus, remove manually.
301
- utils .run_cmd (f"rm -rf { tmp_dir } " )
247
+ utils .run_cmd (f"git clone -b { clone_path } { git_root .strip ()} { clone_path } " )
248
+ return clone_path
302
249
303
250
304
251
# Once we upgrade to python 3.11, this will be in contextlib:
0 commit comments