Skip to content

Commit 88f31b3

Browse files
committed
test_link_result_basic_functionality
1 parent 316ca02 commit 88f31b3

File tree

2 files changed

+89
-2
lines changed

2 files changed

+89
-2
lines changed

util/tests/unittest_workspace.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
make_workspace_structure,
1111
verify_structure,
1212
)
13-
from util.workspace import DBGymWorkspace, save_file
13+
from util.workspace import DBGymWorkspace, link_result, save_file
1414

1515

1616
class WorkspaceTests(unittest.TestCase):
@@ -33,6 +33,7 @@ def setUp(self) -> None:
3333
DBGymWorkspace.num_times_created_this_run = 0
3434

3535
def tearDown(self) -> None:
36+
# You can comment this out if you want to inspect the scratchspace after a test (often used for debugging).
3637
if self.scratchspace_path.exists():
3738
shutil.rmtree(self.scratchspace_path)
3839

@@ -63,6 +64,10 @@ def get_updated_structure_from_workspace_init(
6364
)
6465
return structure
6566

67+
def test_init_fields(self) -> None:
68+
workspace = DBGymWorkspace(self.workspace_path)
69+
self.assertEqual(workspace.app_name, "dbgym")
70+
6671
def test_init_from_nonexistent_workspace(self) -> None:
6772
starting_structure = FilesystemStructure({})
6873
create_structure(self.scratchspace_path, starting_structure)
@@ -97,6 +102,35 @@ def test_init_from_already_initialized_workspace(self) -> None:
97102

98103
self.assertTrue(verify_structure(self.scratchspace_path, ending_structure))
99104

105+
def test_link_result_basic_functionality(self) -> None:
106+
# Setup.
107+
workspace = DBGymWorkspace(self.workspace_path)
108+
ending_structure = WorkspaceTests.get_workspace_init_structure(workspace)
109+
110+
# Make a result file.
111+
result_path = workspace.dbgym_this_run_path / "result.txt"
112+
result_path.touch()
113+
ending_structure["dbgym_workspace"]["task_runs"][
114+
workspace.dbgym_this_run_path.name
115+
]["result.txt"] = ("file",)
116+
117+
# Link the result file.
118+
workspace.link_result(result_path)
119+
ending_structure["dbgym_workspace"]["symlinks"]["dbgym"] = {}
120+
ending_structure["dbgym_workspace"]["symlinks"]["dbgym"]["result.txt.link"] = (
121+
"symlink",
122+
f"dbgym_workspace/task_runs/{workspace.dbgym_this_run_path.name}/result.txt",
123+
)
124+
125+
# Verify structure.
126+
self.assertTrue(verify_structure(self.scratchspace_path, ending_structure))
127+
128+
# TODO: test overriding existing symlink
129+
# TODO: test linking result from another run should raise
130+
# TODO: test that it should link in the agent dir in the links
131+
# TODO: test that it will ignore the directory structure (unlike save which keeps it)
132+
# TODO: test linking a symlink or a non-fully-resolved path
133+
100134

101135
if __name__ == "__main__":
102136
unittest.main()

util/workspace.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ def __init__(self, dbgym_workspace_path: Path):
168168

169169
self.base_dbgym_repo_dpath = get_base_dbgym_repo_dpath()
170170
self.cur_path_list: list[str] = ["dbgym"]
171+
self.app_name = (
172+
"dbgym" # TODO: discover this dynamically. app means dbgym or an agent
173+
)
171174

172175
# Set and create paths.
173176
self.dbgym_workspace_path = dbgym_workspace_path
@@ -220,6 +223,56 @@ def __init__(self, dbgym_workspace_path: Path):
220223
try_remove_file(self.dbgym_latest_run_path)
221224
try_create_symlink(self.dbgym_this_run_path, self.dbgym_latest_run_path)
222225

226+
# TODO(phw2): refactor our manual symlinking in postgres/cli.py to use link_result() instead
227+
def link_result(
228+
self,
229+
result_fordpath: Path,
230+
custom_result_name: Optional[str] = None,
231+
) -> Path:
232+
"""
233+
result_fordpath must be a "result", meaning it was generated inside dbgym_workspace.dbgym_this_run_path.
234+
Further, result_fordpath must have been generated by this invocation to task.py. This also means that
235+
result_fordpath itself can be a file or a dir but not a symlink.
236+
Given a file or directory in task_runs/run_*/[codebase]/[org], this will create a symlink inside
237+
symlinks/[codebase]/[org]/.
238+
Will override the old symlink if there is one, so that symlinks/ always contains the latest generated
239+
version of a file.
240+
This function will return the path to the symlink that was created.
241+
"""
242+
assert isinstance(result_fordpath, Path)
243+
assert is_fully_resolved(
244+
result_fordpath
245+
), f"result_fordpath ({result_fordpath}) should be a fully resolved path"
246+
assert is_child_path(result_fordpath, self.dbgym_this_run_path)
247+
assert not os.path.islink(result_fordpath)
248+
249+
if type(custom_result_name) is str:
250+
result_name = custom_result_name
251+
else:
252+
if os.path.isfile(result_fordpath):
253+
result_name = basename_of_path(result_fordpath) + ".link"
254+
elif os.path.isdir(result_fordpath):
255+
result_name = basename_of_path(result_fordpath) + ".link"
256+
else:
257+
raise AssertionError("result_fordpath must be either a file or dir")
258+
259+
# TODO: check that it was generated in this run_*/.
260+
symlink_parent_dpath = self.dbgym_symlinks_path / self.app_name
261+
symlink_parent_dpath.mkdir(parents=True, exist_ok=True)
262+
263+
# Remove the old symlink ("old" meaning created in an earlier run) if there is one
264+
# Note that in a multi-threaded setting, this might remove one created by a process in the same run,
265+
# meaning it's not "old" by our definition of "old". However, we'll always end up with a symlink
266+
# file of the current run regardless of the order of threads.
267+
assert result_name.endswith(".link") and not result_name.endswith(
268+
".link.link"
269+
), f'result_name ({result_name}) should end with ".link"'
270+
symlink_path = symlink_parent_dpath / result_name
271+
try_remove_file(symlink_path)
272+
try_create_symlink(result_fordpath, symlink_path)
273+
274+
return symlink_path
275+
223276
# `append_group()` is used to mark the "codebase path" of an invocation of the CLI. The "codebase path" is
224277
# explained further in the documentation.
225278
def append_group(self, name: str) -> None:
@@ -564,7 +617,7 @@ def save_file(dbgym_workspace: DBGymWorkspace, fpath: Path) -> None:
564617
shutil.copy(fpath, copy_fpath)
565618

566619

567-
# TODO(phw2): refactor our manual symlinking in postgres/cli.py to use link_result() instead
620+
# TODO(phw2): deprecate this once I'm done with unittest_workspace.py
568621
def link_result(
569622
dbgym_workspace: DBGymWorkspace,
570623
result_fordpath: Path,

0 commit comments

Comments
 (0)