132 lines
4.5 KiB
Swift
132 lines
4.5 KiB
Swift
import XCTest
|
|
import Foundation
|
|
@testable import ForgeProxy
|
|
|
|
final class WatchFolderTests: XCTestCase {
|
|
|
|
var dir: String!
|
|
|
|
override func setUpWithError() throws {
|
|
dir = NSTemporaryDirectory() + "forge-watch-\(UUID().uuidString)"
|
|
try FileManager.default.createDirectory(atPath: dir, withIntermediateDirectories: true)
|
|
}
|
|
|
|
override func tearDown() {
|
|
try? FileManager.default.removeItem(atPath: dir)
|
|
}
|
|
|
|
// New stable file enqueued exactly once.
|
|
func testNewFileEnqueuedOnce() async throws {
|
|
let watcher = WatchFolder(
|
|
path: dir,
|
|
extensions: ["mov"],
|
|
pollInterval: .milliseconds(30),
|
|
stabilityChecks: 2)
|
|
var enqueued = [String]()
|
|
let stream = await watcher.start()
|
|
|
|
try Data("clip".utf8).write(to: URL(fileURLWithPath: dir + "/C001.mov"))
|
|
|
|
let collector = Task {
|
|
for await path in stream {
|
|
enqueued.append(path)
|
|
}
|
|
}
|
|
try await Task.sleep(for: .milliseconds(400))
|
|
await watcher.stop()
|
|
_ = await collector.result
|
|
|
|
XCTAssertEqual(enqueued.count, 1)
|
|
XCTAssertTrue(enqueued[0].hasSuffix("C001.mov"))
|
|
}
|
|
|
|
// Growing file (partial write) not enqueued until size stable.
|
|
func testGrowingFileWaitsForStability() async throws {
|
|
let watcher = WatchFolder(
|
|
path: dir,
|
|
extensions: ["mov"],
|
|
pollInterval: .milliseconds(40),
|
|
stabilityChecks: 2)
|
|
let path = dir + "/GROW.mov"
|
|
try Data("start".utf8).write(to: URL(fileURLWithPath: path))
|
|
|
|
let stream = await watcher.start()
|
|
actor Seen {
|
|
var paths = [String]()
|
|
var at: [ContinuousClock.Instant] = []
|
|
func add(_ p: String) {
|
|
paths.append(p)
|
|
at.append(.now)
|
|
}
|
|
}
|
|
let seen = Seen()
|
|
let collector = Task {
|
|
for await p in stream {
|
|
await seen.add(p)
|
|
}
|
|
}
|
|
|
|
// Keep growing for ~200ms.
|
|
for i in 0..<5 {
|
|
try await Task.sleep(for: .milliseconds(40))
|
|
let h = FileHandle(forWritingAtPath: path)!
|
|
h.seekToEndOfFile()
|
|
h.write(Data("more\(i)".utf8))
|
|
try h.close()
|
|
}
|
|
let growEnd = ContinuousClock.now
|
|
try await Task.sleep(for: .milliseconds(400))
|
|
await watcher.stop()
|
|
_ = await collector.result
|
|
|
|
let paths = await seen.paths
|
|
let times = await seen.at
|
|
XCTAssertEqual(paths.count, 1)
|
|
// Enqueue happened after growth stopped.
|
|
XCTAssertGreaterThan(times[0], growEnd)
|
|
}
|
|
|
|
// Extension filter.
|
|
func testExtensionFilter() async throws {
|
|
let watcher = WatchFolder(path: dir, extensions: ["mov"], pollInterval: .milliseconds(30), stabilityChecks: 1)
|
|
try Data("x".utf8).write(to: URL(fileURLWithPath: dir + "/note.txt"))
|
|
try Data("y".utf8).write(to: URL(fileURLWithPath: dir + "/C002.mov"))
|
|
|
|
let stream = await watcher.start()
|
|
var enqueued = [String]()
|
|
let collector = Task {
|
|
for await p in stream { enqueued.append(p) }
|
|
}
|
|
try await Task.sleep(for: .milliseconds(300))
|
|
await watcher.stop()
|
|
_ = await collector.result
|
|
|
|
XCTAssertEqual(enqueued.count, 1)
|
|
XCTAssertTrue(enqueued[0].hasSuffix(".mov"))
|
|
}
|
|
|
|
// Already-processed file skipped across watcher runs via skip list.
|
|
func testSkipListPersistsAcrossRuns() async throws {
|
|
try Data("x".utf8).write(to: URL(fileURLWithPath: dir + "/C003.mov"))
|
|
|
|
let skipList = ProcessedSkipList()
|
|
let w1 = WatchFolder(path: dir, extensions: ["mov"], pollInterval: .milliseconds(30), stabilityChecks: 1, skipList: skipList)
|
|
let s1 = await w1.start()
|
|
var first = [String]()
|
|
let c1 = Task { for await p in s1 { first.append(p) } }
|
|
try await Task.sleep(for: .milliseconds(250))
|
|
await w1.stop()
|
|
_ = await c1.result
|
|
XCTAssertEqual(first.count, 1)
|
|
|
|
// Second watcher with same skip list: nothing new.
|
|
let w2 = WatchFolder(path: dir, extensions: ["mov"], pollInterval: .milliseconds(30), stabilityChecks: 1, skipList: skipList)
|
|
let s2 = await w2.start()
|
|
var second = [String]()
|
|
let c2 = Task { for await p in s2 { second.append(p) } }
|
|
try await Task.sleep(for: .milliseconds(250))
|
|
await w2.stop()
|
|
_ = await c2.result
|
|
XCTAssertTrue(second.isEmpty)
|
|
}
|
|
}
|