Skip to content

Commit 0b626ba

Browse files
committed
fixed error with file deletion permission
1 parent 939c8d3 commit 0b626ba

2 files changed

Lines changed: 125 additions & 3 deletions

File tree

‎test/test_vcf_rdfizer_unit.py‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,69 @@ def fake_run(cmd, cwd=None, env=None):
601601
self.assertTrue((out_dir / "sample" / "sample.nt").exists())
602602
self.assertTrue((out_dir / "hdt" / "sample.hdt").exists())
603603

604+
def test_main_full_mode_deletes_nt_with_docker_fallback_on_permission_error(self):
605+
"""Full mode falls back to Docker-based removal when .nt unlink raises PermissionError."""
606+
with tempfile.TemporaryDirectory() as td:
607+
tmp_path = Path(td)
608+
input_dir, rules_path = prepare_inputs(tmp_path)
609+
out_dir = tmp_path / "out"
610+
target_nt = out_dir / "sample" / "sample.nt"
611+
target_nt_resolved = target_nt.resolve()
612+
commands = []
613+
original_unlink = Path.unlink
614+
615+
def fake_run(cmd, cwd=None, env=None):
616+
commands.append(cmd)
617+
if "/opt/vcf-rdfizer/run_conversion.sh" in cmd:
618+
target_nt.parent.mkdir(parents=True, exist_ok=True)
619+
target_nt.write_text("<s> <p> <o> .\n")
620+
if "/opt/vcf-rdfizer/compression.sh" in cmd:
621+
hdt_dir = out_dir / "hdt"
622+
hdt_dir.mkdir(parents=True, exist_ok=True)
623+
(hdt_dir / "sample.hdt").write_text("fake-hdt\n")
624+
if isinstance(cmd, list) and cmd[-1].startswith("rm -f ") and "/data/out/sample/sample.nt" in cmd[-1]:
625+
if target_nt_resolved.exists():
626+
original_unlink(target_nt_resolved)
627+
return 0
628+
629+
def unlink_side_effect(path_obj, *args, **kwargs):
630+
if path_obj.resolve() == target_nt_resolved:
631+
raise PermissionError(13, "Permission denied", str(path_obj))
632+
return original_unlink(path_obj, *args, **kwargs)
633+
634+
old_cwd = os.getcwd()
635+
os.chdir(tmp_path)
636+
try:
637+
with mock.patch.object(vcf_rdfizer, "run", side_effect=fake_run), mock.patch.object(
638+
vcf_rdfizer, "check_docker", return_value=True
639+
), mock.patch.object(
640+
vcf_rdfizer, "docker_image_exists", return_value=True
641+
), mock.patch.object(
642+
vcf_rdfizer, "discover_tsv_triplets", return_value=mocked_triplets()
643+
), mock.patch("pathlib.Path.unlink", autospec=True, side_effect=unlink_side_effect):
644+
rc = invoke_main(
645+
[
646+
"--input",
647+
str(input_dir),
648+
"--rules",
649+
str(rules_path),
650+
"--out",
651+
str(out_dir),
652+
"--keep-tsv",
653+
]
654+
)
655+
finally:
656+
os.chdir(old_cwd)
657+
658+
self.assertEqual(rc, 0)
659+
self.assertFalse(target_nt.exists())
660+
self.assertTrue(
661+
any(
662+
isinstance(cmd, list) and cmd[-1].startswith("rm -f ") and "/data/out/sample/sample.nt" in cmd[-1]
663+
for cmd in commands
664+
)
665+
)
666+
604667
def test_main_ignores_unrelated_existing_tsv_triplets(self):
605668
"""Wrapper converts only triplets that match the CLI-selected VCF snapshot."""
606669
with tempfile.TemporaryDirectory() as td:

‎vcf_rdfizer.py‎

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,50 @@ def print_nt_hdt_summary(*, output_root: Path, nt_path: Path, hdt_path: Path):
189189
print(f" - HDT (.hdt): {format_bytes(hdt_size)} ({hdt_path})")
190190

191191

192+
def remove_file_with_docker_fallback(
193+
*,
194+
path: Path,
195+
mount_root: Path,
196+
mount_point: str,
197+
image_ref: str,
198+
wrapper_log_path: Path,
199+
) -> bool:
200+
if not path.exists():
201+
return True
202+
203+
try:
204+
path.unlink()
205+
return True
206+
except PermissionError:
207+
pass
208+
209+
try:
210+
rel = path.relative_to(mount_root)
211+
except ValueError:
212+
eprint(f"Error: cannot remove file outside mounted root: {path}")
213+
eprint(f"See log for details: {wrapper_log_path}")
214+
return False
215+
216+
container_path = f"{mount_point}/{rel.as_posix()}"
217+
rm_cmd = [
218+
"sudo",
219+
"docker",
220+
"run",
221+
"--rm",
222+
"-v",
223+
f"{str(mount_root)}:{mount_point}",
224+
image_ref,
225+
"bash",
226+
"-lc",
227+
f"rm -f {shlex.quote(container_path)}",
228+
]
229+
if run(rm_cmd) != 0:
230+
eprint(f"Error: failed to remove file with Docker fallback: {path}")
231+
eprint(f"See log for details: {wrapper_log_path}")
232+
return False
233+
return True
234+
235+
192236
def existing_parent(path: Path) -> Path:
193237
cur = path
194238
while not cur.exists():
@@ -531,7 +575,14 @@ def run_full_mode(
531575

532576
if not keep_rdf and selected_methods:
533577
if nt_path.exists():
534-
nt_path.unlink()
578+
if not remove_file_with_docker_fallback(
579+
path=nt_path,
580+
mount_root=out_dir,
581+
mount_point="/data/out",
582+
image_ref=image_ref,
583+
wrapper_log_path=wrapper_log_path,
584+
):
585+
return 1
535586
print(f" * Removed N-Triples file (set --keep-rdf to retain): {nt_path}")
536587
else:
537588
print(f" * N-Triples cleanup skipped (not found): {nt_path}")
@@ -541,7 +592,14 @@ def run_full_mode(
541592
if not keep_tsv:
542593
for tsv_path in (triplet["records"], triplet["headers"], triplet["metadata"]):
543594
if tsv_path.exists():
544-
tsv_path.unlink()
595+
if not remove_file_with_docker_fallback(
596+
path=tsv_path,
597+
mount_root=tsv_dir,
598+
mount_point="/data/tsv",
599+
image_ref=image_ref,
600+
wrapper_log_path=wrapper_log_path,
601+
):
602+
return 1
545603

546604
if not keep_tsv:
547605
if not tsv_existed:
@@ -731,9 +789,10 @@ def main():
731789
)
732790
parser.add_argument(
733791
"-q",
792+
"--nq",
734793
"--nt",
735794
"--rdf",
736-
dest="nt",
795+
dest="nq",
737796
default=None,
738797
help="Input RDF file (.nt or .nq) for --mode compress",
739798
)

0 commit comments

Comments
 (0)