Skip to content

Conversation

@matthijs-deltares
Copy link
Contributor

@matthijs-deltares matthijs-deltares commented Jan 26, 2026

This PR introduces the "_work" copy of the test case input directory, which is used for test execution. This will be used to separate the work dir that executes the tests from the tracked dvc directory. The method of interest is __copy_to_work_folder. The rest are code improvements.

@matthijs-deltares matthijs-deltares force-pushed the all/task/DEVOPSDSC-868-use-work-dir branch from 5d0578d to b2c5f10 Compare January 26, 2026 16:18
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a "_work" copy mechanism for test case input directories, separating the working directory used for test execution from the tracked DVC directory. This prevents test execution from modifying tracked files.

Changes:

  • Added DirectoryState dataclass to capture directory snapshots using UTC timestamps instead of raw float values
  • Implemented __copy_to_work_folder method to create and manage work directory copies of input directories
  • Modified path handling to use "_work" suffixed paths for test execution

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
test/deltares_testbench/src/suite/test_case.py Refactored directory state tracking with DirectoryState dataclass and UTC timestamps
test/deltares_testbench/src/suite/test_set_runner.py Added work folder copy logic and updated path handling to use "_work" suffix
test/deltares_testbench/test/suite/test_TestCase.py Added test for runfile changes detection
test/deltares_testbench/test/suite/test_ComparisonRunner.py Added comprehensive tests for work folder creation, copying, and overwriting

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@matthijs-deltares matthijs-deltares force-pushed the all/task/DEVOPSDSC-868-use-work-dir branch from 8e3898e to 84a2ff8 Compare January 27, 2026 07:42
Copy link
Contributor

@savente93 savente93 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

couple of questions and comments but nothing major


def __copy_to_work_folder(self, local_path: str, logger: ILogger) -> None:
"""Copy downloaded files to work folder if needed."""
copy_path = local_path + "_work"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
copy_path = local_path + "_work"
copy_path = Path(local_path) / "_work"

"""Set absolute paths on the config based on location type."""
if location_type == PathType.INPUT:
config.absolute_test_case_path = local_path
config.absolute_test_case_path = local_path + "_work"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd advie to use Path here as well if possible

Comment on lines +19 to +34
@dataclass
class DirectoryState:
"""Snapshot of a directory's state used to detect added/changed files.

Attributes
----------
files : Dict[str, datetime]
Mapping of filename to last modification time (UTC).
size : int
Total size in bytes of all files in the directory at snapshot time.
"""

files: Dict[str, datetime] = field(default_factory=dict)
size: int = 0


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand the point of this class. I see the data gets collected, but I don't see it being used anywhere. Could you explain the thought process behind it?

)
logger = MagicMock(spec=ConsoleLogger)
runner = ComparisonRunner(settings, logger)
run_data = RunData(1, 10)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
run_data = RunData(1, 10)
# get the data of test case nr 10
run_data = RunData(1, 10)

For someone unfamiliar with the code base (like me) I think this comment, or something like it would be very useful

runner.run_test_case(config=config, run_data=run_data)

# Assert
assert fs.exists(expected_work_path)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you want to be thurough you might want to test that the coppied location also has the correct files, but this might be more work than it is worth, so I'll leave it up to you whether you want to do this.

run_data = RunData(1, 1)

expected_local_input_path = "/cases/win64/Name_1"
expected_work_path = expected_local_input_path + "_work"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

especially if these are windows cases (as I assume from the win64 in the path) then it's important to use Path objects here

Comment on lines +432 to +433
expected_local_input_path = "/cases/win64/Name_1"
expected_work_path = expected_local_input_path + "_work"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, use Path objects here

shutil.copytree(local_path, copy_path, symlinks=False, ignore_dangling_symlinks=True)
else:
os.makedirs(os.path.dirname(copy_path) or ".", exist_ok=True)
shutil.copy2(local_path, copy_path)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why copy2? what's the difference with copytree?

@matthijs-deltares matthijs-deltares force-pushed the all/task/DEVOPSDSC-868-use-work-dir branch from 6bd0885 to 0611086 Compare January 28, 2026 09:14
logger.debug(f"Copying input from {local_path} to {copy_path}")
if os.path.isdir(local_path):
shutil.copytree(local_path, copy_path, symlinks=False, ignore_dangling_symlinks=True)
else:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can rule out that local_path is not a directory, right? The input of a case should always be a directory. Maybe do a check at the start of this method: local_path.is_dir() and raise the builtin NotADirectoryError. Then you can skip this edgecase.

raise TestBenchError(error_message) from e

def __copy_to_work_folder(self, local_path: str, logger: ILogger) -> None:
"""Copy downloaded files to work folder if needed."""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that local_path should be a Path instead of a str. What if there's a trailing slash in the string in some config files or something. Then you get <path>/_work, or maybe <path>\_work depending on the platform. The os.path functionality can all be replaced by Path methods. Path implements the PathLike protocol, which shutil.copytree accepts as arguments. So you don't even need to convert the Paths to str before passing them to shutil.copytree

"""Set absolute paths on the config based on location type."""
if location_type == PathType.INPUT:
config.absolute_test_case_path = local_path
config.absolute_test_case_path = local_path + "_work"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

config.absolute_test_case_path is a str for historical reasons, right? I suggest something like this to avoid problems with path separators.

local = Path(local_path)
config.absolute_test_case_path = str(local.parent / (local.name + "_work"))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants