705bb1e6b3
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/462
91 lines
3.9 KiB
Python
91 lines
3.9 KiB
Python
"""缓存上传残块清理只触碰已确认结束的过期自有文件。"""
|
|
|
|
import base64
|
|
import os
|
|
from pathlib import Path
|
|
import tempfile
|
|
import unittest
|
|
|
|
from gitea_cache_upload_cleanup import cleanup_upload_chunks, decode_owner
|
|
|
|
|
|
class UploadCleanupTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.temp.cleanup)
|
|
self.root = Path(self.temp.name).resolve()
|
|
|
|
def file(self, name, run=12, age=10):
|
|
directory = self.root / "tmp-upload" / f"run-{run}-v4"
|
|
directory.mkdir(parents=True, exist_ok=True)
|
|
path = directory / name
|
|
path.write_bytes(b"block data")
|
|
os.utime(path, (age, age))
|
|
return path
|
|
|
|
def block(self, artifact=34, run=12, age=10, marker="genarrative-rust-cache-v1:backend-tests:1:00000000"):
|
|
encoded = base64.urlsafe_b64encode(base64.b64encode(marker.encode())).decode()
|
|
return self.file(f"block-{run}-{artifact}-10-{encoded}", run, age)
|
|
|
|
def test_old_owned_blocks_and_associated_blocklist_are_deleted(self):
|
|
block = self.block()
|
|
blocklist = self.file("12-34-blocklist")
|
|
self.assertEqual(cleanup_upload_chunks(self.root, {12}, 20), 2)
|
|
self.assertFalse(block.exists())
|
|
self.assertFalse(blocklist.exists())
|
|
self.assertTrue(block.parent.is_dir())
|
|
|
|
def test_other_runs_artifacts_and_unproven_blocklists_remain(self):
|
|
own = self.block()
|
|
other_run = self.block(run=13)
|
|
other_artifact = self.block(artifact=35, marker="unrelated-upload")
|
|
unproven = self.file("12-36-blocklist")
|
|
self.assertEqual(cleanup_upload_chunks(self.root, {12}, 20), 1)
|
|
self.assertFalse(own.exists())
|
|
self.assertTrue(all(path.exists() for path in (other_run, other_artifact, unproven)))
|
|
|
|
def test_young_or_unknown_member_protects_entire_artifact_group(self):
|
|
own = self.block()
|
|
young = self.file("12-34-blocklist", age=20)
|
|
other = self.block(artifact=35)
|
|
unknown = self.file("block-12-35-invalid-size-unknown")
|
|
self.assertEqual(cleanup_upload_chunks(self.root, {12}, 20), 0)
|
|
self.assertTrue(all(path.exists() for path in (own, young, other, unknown)))
|
|
|
|
def test_completely_unknown_file_protects_entire_run(self):
|
|
block = self.block()
|
|
self.file("unknown")
|
|
self.assertEqual(cleanup_upload_chunks(self.root, {12}, 20), 0)
|
|
self.assertTrue(block.exists())
|
|
|
|
@unittest.skipUnless(os.name == "posix", "symlink fixture requires POSIX")
|
|
def test_symlinks_never_traversed_or_deleted(self):
|
|
block = self.block()
|
|
outside = self.root / "secret"
|
|
outside.write_text("keep")
|
|
(block.parent / "12-34-blocklist").symlink_to(outside)
|
|
(self.root / "tmp-upload" / "run-13-v4").symlink_to(block.parent, target_is_directory=True)
|
|
self.assertEqual(cleanup_upload_chunks(self.root, {12, 13}, 20), 0)
|
|
self.assertTrue(block.exists())
|
|
self.assertEqual(outside.read_text(), "keep")
|
|
linked = self.root / "root-link"
|
|
linked.symlink_to(self.root, target_is_directory=True)
|
|
with self.assertRaises(ValueError):
|
|
cleanup_upload_chunks(linked, {12}, 20)
|
|
|
|
def test_root_missing_or_relative_is_an_error(self):
|
|
with self.assertRaises(FileNotFoundError):
|
|
cleanup_upload_chunks(self.root / "missing", {12}, 20)
|
|
with self.assertRaises(ValueError):
|
|
cleanup_upload_chunks(Path("relative"), {12}, 20)
|
|
|
|
def test_noncanonical_encoding_and_unknown_job_are_not_owned(self):
|
|
for marker in ("genarrative-rust-cache-v1:other-job:1:00000000", "genarrative-rust-cache-v1:backend-tests:01:00000000"):
|
|
encoded = base64.urlsafe_b64encode(base64.b64encode(marker.encode())).decode()
|
|
self.assertIsNone(decode_owner(encoded))
|
|
self.assertIsNone(decode_owner("not-base64"))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|