Skip to content

Commit f55fbef

Browse files
authored
Add OSFileSystem to complement MockFileSystem (#7927)
This expands available `AsyncFileSystem` APIs and brings them in sync with `swift-sdk-generator` as implemented in swiftlang/swift-sdk-generator#136.
1 parent ca3076a commit f55fbef

File tree

4 files changed

+110
-2
lines changed

4 files changed

+110
-2
lines changed

Sources/_AsyncFileSystem/MockFileSystem.swift

+8
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ package actor MockFileSystem: AsyncFileSystem {
4545
self.storage.content.keys.contains(path)
4646
}
4747

48+
/// Writes a sequence of bytes to a file. Any existing content is replaced with new content.
49+
/// - Parameters:
50+
/// - path: absolute path of the file to write bytes to.
51+
/// - bytes: sequence of bytes to write to file's contents replacing old content.
52+
func write(path: FilePath, bytes: some Sequence<UInt8>) {
53+
storage.content[path] = Array(bytes)
54+
}
55+
4856
/// Appends a sequence of bytes to a file.
4957
/// - Parameters:
5058
/// - path: absolute path of the file to append bytes to.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2023-2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
@preconcurrency package import SystemPackage
15+
16+
public actor OSFileSystem: AsyncFileSystem {
17+
public static let defaultChunkSize = 512 * 1024
18+
19+
let readChunkSize: Int
20+
private let ioQueue = DispatchQueue(label: "org.swift.sdk-generator-io")
21+
22+
package init(readChunkSize: Int = defaultChunkSize) {
23+
self.readChunkSize = readChunkSize
24+
}
25+
26+
package func withOpenReadableFile<T: Sendable>(
27+
_ path: FilePath,
28+
_ body: (OpenReadableFile) async throws -> T
29+
) async throws -> T {
30+
let fd = try FileDescriptor.open(path, .readOnly)
31+
// Can't use ``FileDescriptor//closeAfter` here, as that doesn't support async closures.
32+
do {
33+
let result = try await body(.init(chunkSize: readChunkSize, fileHandle: .real(fd, self.ioQueue)))
34+
try fd.close()
35+
return result
36+
} catch {
37+
try fd.close()
38+
throw error.attach(path)
39+
}
40+
}
41+
42+
package func withOpenWritableFile<T: Sendable>(
43+
_ path: FilePath,
44+
_ body: (OpenWritableFile) async throws -> T
45+
) async throws -> T {
46+
let fd = try FileDescriptor.open(
47+
path,
48+
.writeOnly,
49+
options: [.create, .truncate],
50+
permissions: [
51+
.groupRead,
52+
.otherRead,
53+
.ownerReadWrite
54+
]
55+
)
56+
do {
57+
let result = try await body(.init(storage: .real(fd, self.ioQueue), path: path))
58+
try fd.close()
59+
return result
60+
} catch {
61+
try fd.close()
62+
throw error.attach(path)
63+
}
64+
}
65+
66+
package func exists(_ path: SystemPackage.FilePath) async -> Bool {
67+
FileManager.default.fileExists(atPath: path.string)
68+
}
69+
}

Sources/_AsyncFileSystem/OpenWritableFile.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ package actor OpenWritableFile: WritableStream {
5959
}
6060
}
6161
case let .mock(storage):
62-
await storage.append(path: self.path, bytes: bytes)
62+
await storage.write(path: self.path, bytes: bytes)
6363
}
6464
}
6565

Tests/_AsyncFileSystemTests/AsyncFileSystemTests.swift

+32-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@ final class AsyncFileSystemTests: XCTestCase {
2323

2424
let mockContent = "baz".utf8
2525

26-
try await fs.write(mockPath, bytes: "baz".utf8)
26+
try await fs.write(mockPath, bytes: mockContent)
27+
28+
await XCTAssertAsyncTrue(await fs.exists(mockPath))
29+
30+
// Test overwriting
31+
try await fs.write(mockPath, bytes: mockContent)
2732

2833
await XCTAssertAsyncTrue(await fs.exists(mockPath))
2934

@@ -33,4 +38,30 @@ final class AsyncFileSystemTests: XCTestCase {
3338

3439
XCTAssertEqual(bytes, Array(mockContent))
3540
}
41+
func testOSFileSystem() async throws {
42+
try await testWithTemporaryDirectory { tmpDir in
43+
let fs = OSFileSystem()
44+
45+
let mockPath = FilePath(tmpDir.appending("foo").pathString)
46+
47+
await XCTAssertAsyncFalse(await fs.exists(mockPath))
48+
49+
let mockContent = "baz".utf8
50+
51+
try await fs.write(mockPath, bytes: mockContent)
52+
53+
await XCTAssertAsyncTrue(await fs.exists(mockPath))
54+
55+
// Test overwriting
56+
try await fs.write(mockPath, bytes: mockContent)
57+
58+
await XCTAssertAsyncTrue(await fs.exists(mockPath))
59+
60+
let bytes = try await fs.withOpenReadableFile(mockPath) { fileHandle in
61+
try await fileHandle.read().reduce(into: []) { $0.append(contentsOf: $1) }
62+
}
63+
64+
XCTAssertEqual(bytes, Array(mockContent))
65+
}
66+
}
3667
}

0 commit comments

Comments
 (0)