1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2025-08-06 11:30: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,38 +46,49 @@ 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
if files[path] != nil { try accessQueue.sync {
throw TestableFileSystemError.alreadyExists if files[path] != nil {
throw TestableFileSystemError.alreadyExists
}
self.createIntermediateDirectories(path)
self.files[path] = .fake(.directory)
} }
self.createIntermediateDirectories(path)
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
if files[path] != nil { try accessQueue.sync {
throw TestableFileSystemError.alreadyExists if files[path] != nil {
} 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
guard let file = files[path] else { return try accessQueue.sync {
throw TestableFileSystemError.fileMissing guard let file = files[path] else {
} throw TestableFileSystemError.fileMissing
}
return file.content ?? "" return file.content ?? ""
}
} }
func getShallowContentsOfDirectory(_ path: String) throws -> [String] { func getShallowContentsOfDirectory(_ path: String) throws -> [String] {
@ -88,32 +99,36 @@ class TestableFileSystem: FileSystemProtocol {
seek = "\(seek)/" seek = "\(seek)/"
} }
return self.files.keys return accessQueue.sync {
.filter { $0.hasPrefix(seek) } self.files.keys
.map { $0.replacingOccurrences(of: seek, with: "") } .filter { $0.hasPrefix(seek) }
.filter { !$0.contains("/") } .map { $0.replacingOccurrences(of: seek, with: "") }
.filter { !$0.contains("/") }
}
} }
func getDestinationOfSymlink(_ path: String) throws -> String { func getDestinationOfSymlink(_ path: String) throws -> String {
let path = path.replacingTildeWithHomeDirectory let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else { return try accessQueue.sync {
throw TestableFileSystemError.fileMissing guard let file = files[path] else {
} throw TestableFileSystemError.fileMissing
}
if file.type != .symlink { if file.type != .symlink {
throw TestableFileSystemError.notSymlink throw TestableFileSystemError.notSymlink
} }
guard let pathToSymlink = file.content else { guard let pathToSymlink = file.content else {
throw TestableFileSystemError.invalidSymlink throw TestableFileSystemError.invalidSymlink
} }
if !files.keys.contains(pathToSymlink) { if !files.keys.contains(pathToSymlink) {
throw TestableFileSystemError.invalidSymlink throw TestableFileSystemError.invalidSymlink
} }
return pathToSymlink return pathToSymlink
}
} }
// MARK: - Move & Delete Files // MARK: - Move & Delete Files
@ -122,27 +137,31 @@ class TestableFileSystem: FileSystemProtocol {
let path = path.replacingTildeWithHomeDirectory let path = path.replacingTildeWithHomeDirectory
let newPath = newPath.replacingTildeWithHomeDirectory let newPath = newPath.replacingTildeWithHomeDirectory
self.files.keys.forEach { key in accessQueue.sync {
if key.hasPrefix(path) { self.files.keys.forEach { key in
self.files.renameKey( if key.hasPrefix(path) {
fromKey: key, self.files.renameKey(
toKey: key.replacingOccurrences(of: path, with: newPath) fromKey: key,
) toKey: key.replacingOccurrences(of: path, with: newPath)
)
}
} }
}
self.files.renameKey(fromKey: path, toKey: newPath) self.files.renameKey(fromKey: path, toKey: newPath)
}
} }
func remove(_ path: String) throws { func remove(_ path: String) throws {
// Remove recursively accessQueue.sync {
self.files.keys.forEach { key in // Remove recursively
if key.hasPrefix(path) { self.files.keys.forEach { key in
self.files.removeValue(forKey: key) if key.hasPrefix(path) {
self.files.removeValue(forKey: key)
}
} }
}
self.files.removeValue(forKey: path) self.files.removeValue(forKey: path)
}
} }
// MARK: Attributes // MARK: Attributes
@ -150,11 +169,13 @@ class TestableFileSystem: FileSystemProtocol {
func makeExecutable(_ path: String) throws { func makeExecutable(_ path: String) throws {
let path = path.replacingTildeWithHomeDirectory let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else { try accessQueue.sync {
throw TestableFileSystemError.fileMissing guard let file = files[path] else {
} throw TestableFileSystemError.fileMissing
}
file.type = .binary file.type = .binary
}
} }
// MARK: - Checks // MARK: - Checks
@ -162,93 +183,107 @@ class TestableFileSystem: FileSystemProtocol {
func isExecutableFile(_ path: String) -> Bool { func isExecutableFile(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory let path = path.replacingTildeWithHomeDirectory
guard let file = files[path.replacingTildeWithHomeDirectory] else { return accessQueue.sync {
return false guard let file = files[path.replacingTildeWithHomeDirectory] else {
} 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
guard let file = files[path.replacingTildeWithHomeDirectory] else { return accessQueue.sync {
return false guard let file = files[path.replacingTildeWithHomeDirectory] else {
} 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
guard let file = files[path] else { return accessQueue.sync {
return false guard let file = files[path] else {
} 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
guard let file = files[path] else { return accessQueue.sync {
return false guard let file = files[path] else {
} 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
guard let file = files[path] else { return accessQueue.sync {
return false guard let file = files[path] else {
} 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
guard let file = files[path] else { return accessQueue.sync {
return false guard let file = files[path] else {
} return false
}
return file.type == .directory return file.type == .directory
}
} }
public func printContents() { public func printContents() {
for key in self.files.keys.sorted() { accessQueue.sync {
print("\(key) -> \(self.files[key]!.type)") for key in self.files.keys.sorted() {
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 = ""
var directoriesToCreate: [String] = []
for item in items { for item in items {
let key = preceding == "/" let key = preceding == "/" ? "/\(item)" : "\(preceding)/\(item)"
? "/\(item)" directoriesToCreate.append(key)
: "\(preceding)/\(item)"
if !self.files.keys.contains(key) {
self.files[key] = .fake(.directory)
}
preceding = key preceding = key
} }
for key in directoriesToCreate where !self.files.keys.contains(key) {
self.files[key] = .fake(.directory)
}
} }
} }