From 82c3411fa23f784bb2673bdfb85c9a847d598fb2 Mon Sep 17 00:00:00 2001 From: yoshikazu Date: Mon, 27 Jul 2026 15:45:15 +0000 Subject: [PATCH] Add fingerprint cache tests --- .../PhotochkTests/FingerprintCacheTests.swift | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 Tests/PhotochkTests/FingerprintCacheTests.swift diff --git a/Tests/PhotochkTests/FingerprintCacheTests.swift b/Tests/PhotochkTests/FingerprintCacheTests.swift new file mode 100644 index 0000000..b86ac33 --- /dev/null +++ b/Tests/PhotochkTests/FingerprintCacheTests.swift @@ -0,0 +1,81 @@ +import Foundation +import Testing +import Vision + +@testable import Photochk + +@Suite("キャッシュ") +struct FingerprintCacheTests { + private func makeFile(at directory: URL) throws -> ScannedFile { + let url = directory.appendingPathComponent("cached.jpg") + try TestImages.writeJPEG(TestImages.pattern(seed: 3), to: url, quality: 0.9) + + let values = try url.resourceValues(forKeys: [.fileSizeKey, .contentModificationDateKey]) + return ScannedFile( + url: url, + fileSize: Int64(values.fileSize ?? 0), + modificationDate: values.contentModificationDate ?? Date(), + creationDate: nil + ) + } + + @Test("dHash と feature print がディスクを経由して復元できる") + func roundTrip() async throws { + let directory = try TestImages.makeTemporaryDirectory() + defer { try? FileManager.default.removeItem(at: directory) } + + let file = try makeFile(at: directory) + let cacheURL = directory.appendingPathComponent("cache.plist") + + let basics = try #require(DHash.compute(for: file.url)) + let print = try await FeaturePrinter.compute(for: file.url) + + let writer = FingerprintCache(fileURL: cacheURL) + await writer.store(basics: [file.url: basics], for: [file]) + await writer.store(featurePrints: [file.url: print], for: [file]) + await writer.save() + + let reader = FingerprintCache(fileURL: cacheURL) + let restoredBasics = await reader.basics(for: [file]) + let restoredPrints = await reader.featurePrints(for: [file]) + + #expect(restoredBasics[file.url]?.dHash == basics.dHash) + let restoredPrint = try #require(restoredPrints[file.url]) + // 同一の特徴量なので距離は 0 + #expect(try restoredPrint.distance(to: print) == 0) + } + + @Test("ファイルが更新されていたらキャッシュは使われない") + func invalidatesOnChange() async throws { + let directory = try TestImages.makeTemporaryDirectory() + defer { try? FileManager.default.removeItem(at: directory) } + + let file = try makeFile(at: directory) + let cache = FingerprintCache(fileURL: nil) + let basics = try #require(DHash.compute(for: file.url)) + await cache.store(basics: [file.url: basics], for: [file]) + + let changed = ScannedFile( + url: file.url, + fileSize: file.fileSize + 100, + modificationDate: file.modificationDate, + creationDate: nil + ) + #expect(await cache.basics(for: [changed]).isEmpty) + #expect(await cache.basics(for: [file]).count == 1) + } + + @Test("消えたファイルのエントリは prune で捨てられる") + func prunesMissingFiles() async throws { + let directory = try TestImages.makeTemporaryDirectory() + defer { try? FileManager.default.removeItem(at: directory) } + + let file = try makeFile(at: directory) + let cache = FingerprintCache(fileURL: nil) + let basics = try #require(DHash.compute(for: file.url)) + await cache.store(basics: [file.url: basics], for: [file]) + + await cache.prune(keeping: []) + #expect(await cache.basics(for: [file]).isEmpty) + } +}