1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2025-08-07 03:50:08 +02:00

🐛 Serial dispatch queue for test FS

This commit is contained in:
2024-11-26 14:17:06 +01:00
parent 9f1761d68e
commit f7b1679e97

View File

@ -18,11 +18,11 @@ class TestableFileSystem: FileSystemProtocol {
self.files = files self.files = files
// Ensure that each of the ~ characters are replaced with the home directory path // Ensure that each of the ~ characters are replaced with the home directory path
for key in self.files.keys where key.contains("~") { accessQueue.sync {
self.files.renameKey( for (key, value) in files {
fromKey: key, let adjustedKey = key.contains("~") ? key.replacingOccurrences(of: "~", with: self.homeDirectory) : key
toKey: key.replacingOccurrences(of: "~", with: self.homeDirectory) self.files[adjustedKey] = value
) }
} }
// Ensure that intermediate directories are created // Ensure that intermediate directories are created
@ -46,11 +46,17 @@ class TestableFileSystem: FileSystemProtocol {
*/ */
private(set) var homeDirectory = "/Users/fake" private(set) var homeDirectory = "/Users/fake"
/**
Serial dispatch queue for ensuring thread-safe access to the `files` dictionary.
*/
private let accessQueue = DispatchQueue(label: "com.testablefilesystem.accessQueue")
// MARK: - Basics // MARK: - Basics
func createDirectory(_ path: String, withIntermediateDirectories: Bool) throws { func createDirectory(_ path: String, withIntermediateDirectories: Bool) throws {
let path = path.replacingTildeWithHomeDirectory let path = path.replacingTildeWithHomeDirectory
try accessQueue.sync {
if files[path] != nil { if files[path] != nil {
throw TestableFileSystemError.alreadyExists throw TestableFileSystemError.alreadyExists
} }
@ -59,26 +65,31 @@ class TestableFileSystem: FileSystemProtocol {
self.files[path] = .fake(.directory) self.files[path] = .fake(.directory)
} }
}
func writeAtomicallyToFile(_ path: String, content: String) throws { func writeAtomicallyToFile(_ path: String, content: String) throws {
let path = path.replacingTildeWithHomeDirectory let path = path.replacingTildeWithHomeDirectory
try accessQueue.sync {
if files[path] != nil { if files[path] != nil {
throw TestableFileSystemError.alreadyExists throw TestableFileSystemError.alreadyExists
} }
self.files[path] = .fake(.text, content) self.files[path] = .fake(.text, content)
} }
}
func getStringFromFile(_ path: String) throws -> String { func getStringFromFile(_ path: String) throws -> String {
let path = path.replacingTildeWithHomeDirectory let path = path.replacingTildeWithHomeDirectory
return try accessQueue.sync {
guard let file = files[path] else { guard let file = files[path] else {
throw TestableFileSystemError.fileMissing throw TestableFileSystemError.fileMissing
} }
return file.content ?? "" return file.content ?? ""
} }
}
func getShallowContentsOfDirectory(_ path: String) throws -> [String] { func getShallowContentsOfDirectory(_ path: String) throws -> [String] {
let path = path.replacingTildeWithHomeDirectory let path = path.replacingTildeWithHomeDirectory
@ -88,15 +99,18 @@ class TestableFileSystem: FileSystemProtocol {
seek = "\(seek)/" seek = "\(seek)/"
} }
return self.files.keys return accessQueue.sync {
self.files.keys
.filter { $0.hasPrefix(seek) } .filter { $0.hasPrefix(seek) }
.map { $0.replacingOccurrences(of: seek, with: "") } .map { $0.replacingOccurrences(of: seek, with: "") }
.filter { !$0.contains("/") } .filter { !$0.contains("/") }
} }
}
func getDestinationOfSymlink(_ path: String) throws -> String { func getDestinationOfSymlink(_ path: String) throws -> String {
let path = path.replacingTildeWithHomeDirectory let path = path.replacingTildeWithHomeDirectory
return try accessQueue.sync {
guard let file = files[path] else { guard let file = files[path] else {
throw TestableFileSystemError.fileMissing throw TestableFileSystemError.fileMissing
} }
@ -115,6 +129,7 @@ class TestableFileSystem: FileSystemProtocol {
return pathToSymlink return pathToSymlink
} }
}
// MARK: - Move & Delete Files // MARK: - Move & Delete Files
@ -122,6 +137,7 @@ class TestableFileSystem: FileSystemProtocol {
let path = path.replacingTildeWithHomeDirectory let path = path.replacingTildeWithHomeDirectory
let newPath = newPath.replacingTildeWithHomeDirectory let newPath = newPath.replacingTildeWithHomeDirectory
accessQueue.sync {
self.files.keys.forEach { key in self.files.keys.forEach { key in
if key.hasPrefix(path) { if key.hasPrefix(path) {
self.files.renameKey( self.files.renameKey(
@ -133,8 +149,10 @@ class TestableFileSystem: FileSystemProtocol {
self.files.renameKey(fromKey: path, toKey: newPath) self.files.renameKey(fromKey: path, toKey: newPath)
} }
}
func remove(_ path: String) throws { func remove(_ path: String) throws {
accessQueue.sync {
// Remove recursively // Remove recursively
self.files.keys.forEach { key in self.files.keys.forEach { key in
if key.hasPrefix(path) { if key.hasPrefix(path) {
@ -144,110 +162,127 @@ class TestableFileSystem: FileSystemProtocol {
self.files.removeValue(forKey: path) self.files.removeValue(forKey: path)
} }
}
// MARK: Attributes // MARK: Attributes
func makeExecutable(_ path: String) throws { func makeExecutable(_ path: String) throws {
let path = path.replacingTildeWithHomeDirectory let path = path.replacingTildeWithHomeDirectory
try accessQueue.sync {
guard let file = files[path] else { guard let file = files[path] else {
throw TestableFileSystemError.fileMissing throw TestableFileSystemError.fileMissing
} }
file.type = .binary file.type = .binary
} }
}
// MARK: - Checks // MARK: - Checks
func isExecutableFile(_ path: String) -> Bool { func isExecutableFile(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory let path = path.replacingTildeWithHomeDirectory
return accessQueue.sync {
guard let file = files[path.replacingTildeWithHomeDirectory] else { guard let file = files[path.replacingTildeWithHomeDirectory] else {
return false return false
} }
return file.type == .binary return file.type == .binary
} }
}
func isWriteableFile(_ path: String) -> Bool { func isWriteableFile(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory let path = path.replacingTildeWithHomeDirectory
return accessQueue.sync {
guard let file = files[path.replacingTildeWithHomeDirectory] else { guard let file = files[path.replacingTildeWithHomeDirectory] else {
return false return false
} }
return !file.readOnly return !file.readOnly
} }
}
func anyExists(_ path: String) -> Bool { func anyExists(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory let path = path.replacingTildeWithHomeDirectory
return files.keys.contains(path) return accessQueue.sync {
files.keys.contains(path)
}
} }
func fileExists(_ path: String) -> Bool { func fileExists(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory let path = path.replacingTildeWithHomeDirectory
return accessQueue.sync {
guard let file = files[path] else { guard let file = files[path] else {
return false return false
} }
return [.binary, .symlink, .text].contains(file.type) return [.binary, .symlink, .text].contains(file.type)
} }
}
func directoryExists(_ path: String) -> Bool { func directoryExists(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory let path = path.replacingTildeWithHomeDirectory
return accessQueue.sync {
guard let file = files[path] else { guard let file = files[path] else {
return false return false
} }
return [.directory].contains(file.type) return [.directory].contains(file.type)
} }
}
func isSymlink(_ path: String) -> Bool { func isSymlink(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory let path = path.replacingTildeWithHomeDirectory
return accessQueue.sync {
guard let file = files[path] else { guard let file = files[path] else {
return false return false
} }
return file.type == .symlink return file.type == .symlink
} }
}
func isDirectory(_ path: String) -> Bool { func isDirectory(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory let path = path.replacingTildeWithHomeDirectory
return accessQueue.sync {
guard let file = files[path] else { guard let file = files[path] else {
return false return false
} }
return file.type == .directory return file.type == .directory
} }
}
public func printContents() { public func printContents() {
accessQueue.sync {
for key in self.files.keys.sorted() { for key in self.files.keys.sorted() {
print("\(key) -> \(self.files[key]!.type)") print("\(key) -> \(self.files[key]!.type)")
} }
} }
}
private func createIntermediateDirectories(_ path: String) { private func createIntermediateDirectories(_ path: String) {
let path = path.replacingTildeWithHomeDirectory let path = path.replacingTildeWithHomeDirectory
let items = path.components(separatedBy: "/") let items = path.components(separatedBy: "/")
var preceding = "" var preceding = ""
for item in items { var directoriesToCreate: [String] = []
let key = preceding == "/"
? "/\(item)"
: "\(preceding)/\(item)"
if !self.files.keys.contains(key) { for item in items {
self.files[key] = .fake(.directory) let key = preceding == "/" ? "/\(item)" : "\(preceding)/\(item)"
directoriesToCreate.append(key)
preceding = key
} }
preceding = key for key in directoriesToCreate where !self.files.keys.contains(key) {
self.files[key] = .fake(.directory)
} }
} }
} }