diff --git a/src/algokit/core/goal.py b/src/algokit/core/goal.py index df9750eb..20381fc1 100644 --- a/src/algokit/core/goal.py +++ b/src/algokit/core/goal.py @@ -41,40 +41,56 @@ def list_files_in_volume(volume_path: Path) -> list[str]: def preprocess_command_args( command: list[str], volume_mount_path_local: Path, docker_mount_path_local: Path -) -> tuple[list[str], list[Path], list[str]]: - input_filenames = [] - output_filenames = [] +) -> tuple[list[Path], list[Path], list[str]]: + input_files = [] + output_files = [] try: for i, arg in enumerate(command): if is_path_or_filename(arg): - arg_path = Path(arg) - arg_changed = docker_mount_path_local.joinpath(arg_path.name) + absolute_arg_path = Path(arg).expanduser().absolute() + arg_changed = docker_mount_path_local.joinpath(absolute_arg_path.name) command[i] = str(arg_changed) - file_exists = arg_path.exists() or Path.cwd().joinpath(arg_path.name).exists() - is_output_arg = i > 0 and command[i - 1] in ["-o", "--outdir", "--outfile"] + file_exists = absolute_arg_path.exists() + is_output_arg = i > 0 and command[i - 1] in [ + "-o", + "--outdir", + "--outfile", + "--out", + "--result-out", + "--lsig-out", + ] if file_exists and not is_output_arg: - input_filenames.append(arg_path.name) - shutil.copy(arg_path, volume_mount_path_local) - elif is_output_arg: # it is an output file that is not exist now - output_filenames.append(arg_path) + input_files.append(absolute_arg_path) + shutil.copy(absolute_arg_path, volume_mount_path_local) + elif is_output_arg: # it is an output file that doesn't exist yet + output_files.append(absolute_arg_path) else: raise FileNotFoundError(f"{arg} does not exist.") except Exception as e: logger.error(e) raise e - return input_filenames, output_filenames, command + return input_files, output_files, command -def post_process(input_filenames: list, output_filenames: list[Path], volume_mount_path_local: Path) -> None: - for input_filename in input_filenames: - delete_files_from_volume_mount(input_filename, volume_mount_path_local) +def post_process(input_files: list[Path], output_files: list[Path], volume_mount_path_local: Path) -> None: + for input_file in input_files: + delete_files_from_volume_mount(input_file.name, volume_mount_path_local) - files_in_volume_mount = {Path(file).name for file in list_files_in_volume(volume_mount_path_local)} - for output_filename in output_filenames: - if output_filename.name in files_in_volume_mount: - target_path = ( - output_filename if output_filename.is_absolute() else Path.cwd().joinpath(output_filename.name) + files_in_volume_mount = {Path(file) for file in list_files_in_volume(volume_mount_path_local)} + for output_file in output_files: + stem = output_file.stem + ext = output_file.suffix + + # Copy outputs split into multiple files. For example `goal clerk split -i ./input.gtxn -o ./output.txn` + # will produce a file (output-0.txn etc) for each transaction in the group being split. + r = re.compile(rf"^(?:{stem})(?:-[0-9]+)?(?:\{ext})$") if ext else re.compile(rf"^(?:{stem})(?:-[0-9]+)?$") + + matched_files_in_volume_mount = filter(lambda f: (r.match(f.name)), files_in_volume_mount) + + for matched_file_in_volume_mount in matched_files_in_volume_mount: + shutil.copy( + volume_mount_path_local.joinpath(matched_file_in_volume_mount.name), + output_file.parent.joinpath(matched_file_in_volume_mount.name), ) - shutil.copy(volume_mount_path_local.joinpath(output_filename.name), target_path) - delete_files_from_volume_mount(output_filename.name, volume_mount_path_local) + delete_files_from_volume_mount(matched_file_in_volume_mount.name, volume_mount_path_local) diff --git a/tests/goal/test_goal.py b/tests/goal/test_goal.py index 70b37cc2..e59745c8 100644 --- a/tests/goal/test_goal.py +++ b/tests/goal/test_goal.py @@ -356,7 +356,7 @@ def test_goal_postprocess_of_command_args( result = invoke("goal clerk compile approval.teal -o approval.compiled", cwd=cwd) assert result.exit_code == 0 - # check if the output files is no longer in the goal_mount_path + # check if the output files are no longer in the goal_mount_path assert not (mocked_goal_mount_path / "approval.compiled").exists() # check if the input/output file is in the cwd @@ -369,6 +369,49 @@ def test_goal_postprocess_of_command_args( assert (mocked_goal_mount_path / "approval.group.sig.out").exists() +@pytest.mark.usefixtures("_setup_input_files", "_setup_latest_dummy_compose") +@pytest.mark.parametrize("_setup_input_files", [[{"name": "group.gtxn", "content": ""}]], indirect=True) +def test_goal_postprocess_of_single_output_arg_resulting_in_multiple_output_files( + proc_mock: ProcMock, + cwd: Path, + mocked_goal_mount_path: Path, +) -> None: + expected_arguments = [ + "docker", + "exec", + "--interactive", + "--workdir", + "/root", + "algokit_algod", + "goal", + "clerk", + "split", + ] + + def dump_files(cwd: Path) -> None: + (cwd / "group-0.txn").touch() + (cwd / "group-1.txn").touch() + + proc_mock.set_output( + expected_arguments, + output=["Wrote transaction"], + side_effect=dump_files, + side_effect_args={"cwd": mocked_goal_mount_path}, + ) + + result = invoke("goal clerk split -i group.gtxn -o group.txn", cwd=cwd) + assert result.exit_code == 0 + + # check if the output files are no longer in the goal_mount_path + assert not (mocked_goal_mount_path / "group-0.txn").exists() + assert not (mocked_goal_mount_path / "group-1.txn").exists() + + # check if the input/output file is in the cwd + assert (cwd / "group.gtxn").exists() + assert (cwd / "group-0.txn").exists() + assert (cwd / "group-1.txn").exists() + + @pytest.mark.usefixtures("proc_mock") def test_goal_compose_outdated( cwd: Path,