Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 5 additions & 10 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
{
"name": "Swift",
"image": "swift:5.10",
"image": "swift:6.3",
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {
"installZsh": "false",
"username": "vscode",
"userUid": "1000",
"userGid": "1000",
"username": "ubuntu",
"upgradePackages": "false"
},
"ghcr.io/devcontainers/features/git:1": {
Expand All @@ -25,20 +23,17 @@
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {
"swift.path": "/usr/bin",
"lldb.library": "/usr/lib/liblldb.so"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"sswg.swift-lang"
"swiftlang.swift-vscode"
]
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],

// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "swift --version",

// Set `remoteUser` to `root` to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
"remoteUser": "ubuntu"
}
16 changes: 6 additions & 10 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,11 @@ jobs:

build:

runs-on: ubuntu-22.04

runs-on: ubuntu-latest
timeout-minutes: 15
container:
image: swift:latest
steps:
- uses: actions/checkout@v2
- name: Install Swift
uses: swift-actions/setup-swift@v1
with:
swift-version: 5.9
- name: Build
run: swift build -v
- name: Run tests
- uses: actions/checkout@v7
- name: Build and Run Tests
run: swift test -v
9 changes: 7 additions & 2 deletions Tests/FileMonitorTests/FileMonitorExplicitAddTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,15 @@ final class FileMonitorExplicitAddTests: XCTestCase {

let monitor = try FileMonitor(directory: tmp.appendingPathComponent(dir), delegate: watcher)
try monitor.start()
defer {
monitor.stop()
}
AddWatcher.fileChanges = 0

try "hello".write(to: testFile, atomically: false, encoding: .utf8)
XCTAssertTrue(FileManager.default.fileExists(atPath: testFile.path))
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
try? "hello".write(to: testFile, atomically: false, encoding: .utf8)
XCTAssertTrue(FileManager.default.fileExists(atPath: testFile.path))
}
wait(for: [expectation], timeout: 10)

XCTAssertEqual(AddWatcher.fileChanges, 1)
Expand Down
15 changes: 11 additions & 4 deletions Tests/FileMonitorTests/FileMonitorExplicitChangeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,19 @@ final class FileMonitorExplicitChangeTests: XCTestCase {

let monitor = try FileMonitor(directory: tmp.appendingPathComponent(dir), delegate: watcher)
try monitor.start()
defer {
monitor.stop()
}
ChangeWatcher.fileChanges = 0

let fileHandle = try FileHandle(forWritingTo: testFile)
try fileHandle.seekToEnd()
fileHandle.write("append some text".data(using: .utf8)!)
try fileHandle.close()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
do {
let fileHandle = try FileHandle(forWritingTo: testFile)
try fileHandle.seekToEnd()
fileHandle.write("append some text".data(using: .utf8)!)
try fileHandle.close()
} catch {}
}

wait(for: [expectation], timeout: 10)

Expand Down
10 changes: 8 additions & 2 deletions Tests/FileMonitorTests/FileMonitorExplicitDeleteTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,16 @@ final class FileMonitorExplicitDeleteTests: XCTestCase {

let monitor = try FileMonitor(directory: tmp.appendingPathComponent(dir), delegate: watcher)
try monitor.start()
defer {
monitor.stop()
}
ChangeWatcher.fileChanges = 0

try FileManager.default.removeItem(at: testFile)
XCTAssertFalse(FileManager.default.fileExists(atPath: testFile.path))
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
try? FileManager.default.removeItem(at: testFile)
XCTAssertFalse(FileManager.default.fileExists(atPath: testFile.path))
}


wait(for: [expectation], timeout: 10)

Expand Down
3 changes: 3 additions & 0 deletions Tests/FileMonitorTests/FileMonitorFileHasPathTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ final class FileMonitorFileHasPathTests: XCTestCase {

let monitor = try FileMonitor(directory: tmp.appendingPathComponent(dir), delegate: watcher)
try monitor.start()
defer {
monitor.stop()
}
Watcher.fileChanges = 0

try "hello".write(to: testFile, atomically: false, encoding: .utf8)
Expand Down
169 changes: 103 additions & 66 deletions Tests/FileMonitorTests/FileMonitorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,27 @@ import XCTest
import FileMonitorShared

final class FileMonitorTests: XCTestCase {

let tmp = FileManager.default.temporaryDirectory
let dir = String.random(length: 10)

override func setUpWithError() throws {
super.setUp()
let directory = tmp.appendingPathComponent(dir)

func withDirectory(_ operation: (URL) async throws -> Void) async throws {
let directory = FileManager.default.temporaryDirectory.appendingPathComponent(String.random(length: 10))
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
print("Created directory: \(tmp.appendingPathComponent(dir).path)")
do {
try await operation(directory)
} catch {
try? FileManager.default.removeItem(at: directory)
throw error
}
try FileManager.default.removeItem(at: directory)
}

override func tearDownWithError() throws {
try super.tearDownWithError()
let directory = tmp.appendingPathComponent(dir)
func withDirectory(_ operation: (URL) throws -> Void) throws {
let directory = FileManager.default.temporaryDirectory.appendingPathComponent(String.random(length: 10))
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
do {
try operation(directory)
} catch {
try? FileManager.default.removeItem(at: directory)
throw error
}
try FileManager.default.removeItem(at: directory)
}

Expand Down Expand Up @@ -48,83 +54,114 @@ final class FileMonitorTests: XCTestCase {
}

func testLifecycleCreate() throws {
let expectation = expectation(description: "Wait for file creation")
expectation.assertForOverFulfill = false
try withDirectory { dir in
let expectation = expectation(description: "Wait for file creation")
expectation.assertForOverFulfill = false

let testFile = tmp.appendingPathComponent(dir).appendingPathComponent("\(String.random(length: 8)).\(String.random(length: 3))");
let watcher = Watcher(on: testFile) { expectation.fulfill() }
let testFile = dir.appendingPathComponent("\(String.random(length: 8)).\(String.random(length: 3))");
let watcher = Watcher(on: testFile) { expectation.fulfill() }

let monitor = try FileMonitor(directory: tmp.appendingPathComponent(dir), delegate: watcher)
try monitor.start()
Watcher.fileChanges = 0
let monitor = try FileMonitor(directory: dir, delegate: watcher)
try monitor.start()
defer {
monitor.stop()
}
Watcher.fileChanges = 0

FileManager.default.createFile(atPath: testFile.path, contents: "hello".data(using: .utf8))
wait(for: [expectation], timeout: 10)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
FileManager.default.createFile(atPath: testFile.path, contents: "hello".data(using: .utf8))
}
wait(for: [expectation], timeout: 10)

XCTAssertGreaterThan(Watcher.fileChanges, 0)
XCTAssertGreaterThan(Watcher.fileChanges, 0)
}
}

func testLifecycleChange() async throws {
let expectation = expectation(description: "Wait for file creation")
expectation.assertForOverFulfill = false
func testLifecycleChange() throws {
try withDirectory { dir in
let expectation = expectation(description: "Wait for file creation")
expectation.assertForOverFulfill = false

let testFile = tmp.appendingPathComponent(dir).appendingPathComponent("\(String.random(length: 8)).\(String.random(length: 3))");
FileManager.default.createFile(atPath: testFile.path, contents: "hello".data(using: .utf8))
let testFile = dir.appendingPathComponent("\(String.random(length: 8)).\(String.random(length: 3))");
FileManager.default.createFile(atPath: testFile.path, contents: "hello".data(using: .utf8))

let watcher = Watcher(on: testFile) { expectation.fulfill() }
let watcher = Watcher(on: testFile) { expectation.fulfill() }

let monitor = try FileMonitor(directory: tmp.appendingPathComponent(dir), delegate: watcher)
try monitor.start()
Watcher.fileChanges = 0

try "Next New Content".write(toFile: testFile.path, atomically: true, encoding: .utf8)
await fulfillment(of: [expectation], timeout: 10)
monitor.stop()
XCTAssertGreaterThan(Watcher.fileChanges, 0)
let monitor = try FileMonitor(directory: dir, delegate: watcher)
try monitor.start()
defer {
monitor.stop()
}
Watcher.fileChanges = 0

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
try? "Next New Content".write(toFile: testFile.path, atomically: true, encoding: .utf8)
}
wait(for: [expectation], timeout: 10)
XCTAssertGreaterThan(Watcher.fileChanges, 0)
}
}

func testLifecycleChangeAsync() async throws {
let asyncExpectation = XCTestExpectation(description: "Async wait for file creation")

let testFile = tmp.appendingPathComponent(dir).appendingPathComponent("\(String.random(length: 8)).\(String.random(length: 3))");
FileManager.default.createFile(atPath: testFile.path, contents: "hello".data(using: .utf8))

let monitor = try FileMonitor(directory: tmp.appendingPathComponent(dir))
try monitor.start()
Watcher.fileChanges = 0

var events = [FileChange]()
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
try? "New Content".write(toFile: testFile.path, atomically: true, encoding: .utf8)
}
struct TimeoutError: Error {}
try await withDirectory { dir in
let testFile = dir.appendingPathComponent("\(String.random(length: 8)).\(String.random(length: 3))");
FileManager.default.createFile(atPath: testFile.path, contents: "hello".data(using: .utf8))

let monitor = try FileMonitor(directory: dir)
try monitor.start()
defer {
monitor.stop()
}
Watcher.fileChanges = 0

try await withThrowingTaskGroup(of: FileChangeEvent?.self) { group in
group.addTask {
try await Task.sleep(for: .seconds(1))
try "New Content".write(toFile: testFile.path, atomically: false, encoding: .utf8)
print("Wrote to: \(testFile.path)")
return nil
}
group.addTask {
var iterator = monitor.stream.makeAsyncIterator()
return await iterator.next()
}
group.addTask {
try await Task.sleep(for: .seconds(10))
throw TimeoutError()
}

for await event in monitor.stream {
events.append(event)
asyncExpectation.fulfill()
monitor.stop()
_ = try await group.next()
_ = try await group.next()
group.cancelAll()
}
}

await fulfillment(of: [asyncExpectation], timeout: 10)
XCTAssertGreaterThan(events.count, 0)
}

func testLifecycleDelete() throws {
func testLifecycleDelete() async throws {
let expectation = expectation(description: "Wait for file deletion")
expectation.assertForOverFulfill = false

let testFile = tmp.appendingPathComponent(dir).appendingPathComponent("\(String.random(length: 8)).\(String.random(length: 3))");
FileManager.default.createFile(atPath: testFile.path, contents: "hello".data(using: .utf8))
try await withDirectory { dir in
let testFile = dir.appendingPathComponent("\(String.random(length: 8)).\(String.random(length: 3))");
FileManager.default.createFile(atPath: testFile.path, contents: "hello".data(using: .utf8))

let watcher = Watcher(on: testFile) { expectation.fulfill() }
let watcher = Watcher(on: testFile) { expectation.fulfill() }

let monitor = try FileMonitor(directory: tmp.appendingPathComponent(dir), delegate: watcher)
try monitor.start()
Watcher.fileChanges = 0
let monitor = try FileMonitor(directory: dir, delegate: watcher)
try monitor.start()
defer {
monitor.stop()
}
Watcher.fileChanges = 0

try FileManager.default.removeItem(at: testFile)
wait(for: [expectation], timeout: 10)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
try? FileManager.default.removeItem(at: testFile)
}
await fulfillment(of: [expectation], timeout: 10)

XCTAssertGreaterThan(Watcher.fileChanges, 0)
XCTAssertGreaterThan(Watcher.fileChanges, 0)
}
}


Expand Down