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

👌 Fake FS: ~ and intermediate directories

This commit is contained in:
2022-11-01 16:47:45 +01:00
parent fa2d2105c5
commit 5caca85d7a
13 changed files with 192 additions and 25 deletions

View File

@ -574,6 +574,14 @@
C4D3660D29113F20006BD146 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D3660A29113F20006BD146 /* System.swift */; };
C4D3660E29113F20006BD146 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D3660A29113F20006BD146 /* System.swift */; };
C4D36611291140BE006BD146 /* TestableFileSystemTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D3660F291140BE006BD146 /* TestableFileSystemTest.swift */; };
C4D36615291160A1006BD146 /* WIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36614291160A1006BD146 /* WIP.swift */; };
C4D36616291160A1006BD146 /* WIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36614291160A1006BD146 /* WIP.swift */; };
C4D36617291160A1006BD146 /* WIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36614291160A1006BD146 /* WIP.swift */; };
C4D36618291160A1006BD146 /* WIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36614291160A1006BD146 /* WIP.swift */; };
C4D3661A291173EA006BD146 /* DictionaryExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36619291173EA006BD146 /* DictionaryExtension.swift */; };
C4D3661B291173EA006BD146 /* DictionaryExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36619291173EA006BD146 /* DictionaryExtension.swift */; };
C4D3661C291173EA006BD146 /* DictionaryExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36619291173EA006BD146 /* DictionaryExtension.swift */; };
C4D3661D291173EA006BD146 /* DictionaryExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D36619291173EA006BD146 /* DictionaryExtension.swift */; };
C4D5CFCA27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */; };
C4D5CFCB27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */; };
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D8016522B1584700C6DA1B /* Startup.swift */; };
@ -858,6 +866,8 @@
C4D366052911331E006BD146 /* EmptyProxyScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyProxyScanner.swift; sourceTree = "<group>"; };
C4D3660A29113F20006BD146 /* System.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = System.swift; sourceTree = "<group>"; };
C4D3660F291140BE006BD146 /* TestableFileSystemTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableFileSystemTest.swift; sourceTree = "<group>"; };
C4D36614291160A1006BD146 /* WIP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WIP.swift; sourceTree = "<group>"; };
C4D36619291173EA006BD146 /* DictionaryExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DictionaryExtension.swift; sourceTree = "<group>"; };
C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NginxConfigurationFile.swift; sourceTree = "<group>"; };
C4D8016522B1584700C6DA1B /* Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Startup.swift; sourceTree = "<group>"; };
C4D89BC52783C99400A02B68 /* ComposerJson.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerJson.swift; sourceTree = "<group>"; };
@ -1364,6 +1374,7 @@
C4CCBA6B275C567B008C7055 /* PMWindowController.swift */,
C4B5635D276AB09000F12CCB /* VersionExtractor.swift */,
C4D3660A29113F20006BD146 /* System.swift */,
C4D36614291160A1006BD146 /* WIP.swift */,
);
path = Helpers;
sourceTree = "<group>";
@ -1704,6 +1715,7 @@
C4EB53E628553117006F9937 /* ArrayExtension.swift */,
C44B3A4528E5C70100718CB1 /* TimeIntervalExtension.swift */,
C4E2E84928FC1E70003B070C /* DataExtension.swift */,
C4D36619291173EA006BD146 /* DictionaryExtension.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -1932,6 +1944,7 @@
files = (
C47699EF28A2F2A30060FEB8 /* WarningManager.swift in Sources */,
C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */,
C4D3661A291173EA006BD146 /* DictionaryExtension.swift in Sources */,
C4C8900728F0E3EF00CE5E97 /* ActiveFileSystem.swift in Sources */,
C4D8016622B1584700C6DA1B /* Startup.swift in Sources */,
C42C49DB27C2806F0074ABAC /* MainMenu+FixMyValet.swift in Sources */,
@ -2054,6 +2067,7 @@
C476FF9822B0DD830098105B /* Alert.swift in Sources */,
C474B00624C0E98C00066A22 /* LocalNotification.swift in Sources */,
C4D5CFCA27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */,
C4D36615291160A1006BD146 /* WIP.swift in Sources */,
C485707028BF452300539B36 /* WarningsWindowController.swift in Sources */,
C4CE3BBA27B31F670086CA49 /* ComposerWindow.swift in Sources */,
C4D9ADC8277611A0007277F4 /* InternalSwitcher.swift in Sources */,
@ -2130,6 +2144,7 @@
C471E85428F9BB650021E251 /* StatusMenu.swift in Sources */,
C471E85528F9BB650021E251 /* StatusMenu+Items.swift in Sources */,
C471E85628F9BB650021E251 /* DomainListCellProtocol.swift in Sources */,
C4D36617291160A1006BD146 /* WIP.swift in Sources */,
C471E85728F9BB650021E251 /* DomainListTLSCell.swift in Sources */,
C4D366082911331E006BD146 /* EmptyProxyScanner.swift in Sources */,
C471E85828F9BB650021E251 /* DomainListNameCell.swift in Sources */,
@ -2237,6 +2252,7 @@
C471E7D628F9BA8F0021E251 /* RealFileSystem.swift in Sources */,
C471E81728F9BAE80021E251 /* NSMenuExtension.swift in Sources */,
C471E81328F9BAE80021E251 /* XibLoadable.swift in Sources */,
C4D3661C291173EA006BD146 /* DictionaryExtension.swift in Sources */,
C471E7F128F9BAC70021E251 /* PhpVersionNumber.swift in Sources */,
C471E7DC28F9BA8F0021E251 /* ShellProtocol.swift in Sources */,
);
@ -2300,6 +2316,7 @@
C471E8C028F9BB8F0021E251 /* DomainListVC.swift in Sources */,
C471E8C128F9BB8F0021E251 /* DomainListVC+ContextMenu.swift in Sources */,
C471E8C228F9BB8F0021E251 /* DomainListVC+Actions.swift in Sources */,
C4D36618291160A1006BD146 /* WIP.swift in Sources */,
C471E8C328F9BB8F0021E251 /* SelectionVC.swift in Sources */,
C471E8C428F9BB8F0021E251 /* AddSiteVC.swift in Sources */,
C471E8C528F9BB8F0021E251 /* AddProxyVC.swift in Sources */,
@ -2392,6 +2409,7 @@
C4181F1128FAF9330042EA28 /* UITestCase.swift in Sources */,
C471E81F28F9BB290021E251 /* NginxConfigurationFile.swift in Sources */,
C471E7BF28F9B90F0021E251 /* StartupTest.swift in Sources */,
C4D3661D291173EA006BD146 /* DictionaryExtension.swift in Sources */,
C471E80D28F9BAE80021E251 /* ArrayExtension.swift in Sources */,
C471E7CD28F9BA600021E251 /* ShellProtocol.swift in Sources */,
C471E7EC28F9BAC30021E251 /* Events.swift in Sources */,
@ -2424,6 +2442,7 @@
C415D3B82770F294005EF286 /* Actions.swift in Sources */,
54B48B60275F66AE006D90C5 /* Application.swift in Sources */,
C4FE011228084FC200D1DE6D /* SelectionVC.swift in Sources */,
C4D3661B291173EA006BD146 /* DictionaryExtension.swift in Sources */,
C4F780C825D80B75000DBC97 /* DateExtension.swift in Sources */,
C493084B279F331F009C240B /* AddSiteVC.swift in Sources */,
C44A874928905BB000498BC4 /* ProgressVC.swift in Sources */,
@ -2507,6 +2526,7 @@
5489625928313231004F647A /* CreatedFromFile.swift in Sources */,
C471E79328F9B21F0021E251 /* ActiveFileSystem.swift in Sources */,
54D9E0B327E4F51E003B9AD9 /* HotKeysController.swift in Sources */,
C4D36616291160A1006BD146 /* WIP.swift in Sources */,
03E36FE828D9219000636F7F /* ActiveShell.swift in Sources */,
C4B97B79275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */,
C4E2E86528FC2F1B003B070C /* XCPMApplication.swift in Sources */,

View File

@ -80,7 +80,7 @@
</CommandLineArgument>
<CommandLineArgument
argument = "--configuration:~/.phpmon_fconf_working.json"
isEnabled = "NO">
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "--configuration:~/.phpmon_fconf_broken.json"

View File

@ -62,12 +62,16 @@ public class Paths {
}
public static var homePath: String {
// TODO: Depending on the filesystem abstraction, return the correct information
if Shell is RealShell {
if FileSystem is RealFileSystem {
return NSHomeDirectory()
}
return "/Users/\(Paths.whoami)"
if FileSystem is TestableFileSystem {
let fs = FileSystem as! TestableFileSystem
return fs.homeDirectory
}
fatalError("A valid FileSystem must be allowed to return the home path")
}
public static var cellarPath: String {

View File

@ -0,0 +1,17 @@
//
// DictionaryExtension.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 01/11/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Foundation
extension Dictionary {
mutating func renameKey(fromKey: Key, toKey: Key) {
if let entry = removeValue(forKey: fromKey) {
self[toKey] = entry
}
}
}

View File

@ -15,6 +15,7 @@ var FileSystem: FileSystemProtocol {
class ActiveFileSystem {
static var shared: FileSystemProtocol = RealFileSystem()
/** Note: Intermediate directories are not automatically inferred and have to be manually declared. */
public static func useTestable(_ files: [String: FakeFile]) {
Self.shared = TestableFileSystem(files: files)
}

View File

@ -18,7 +18,7 @@ protocol FileSystemProtocol {
func getStringFromFile(_ path: String) throws -> String
func getContentsOfDirectory(_ path: String) throws -> [String]
func getShallowContentsOfDirectory(_ path: String) throws -> [String]
func getDestinationOfSymlink(_ path: String) throws -> String

View File

@ -40,12 +40,14 @@ class RealFileSystem: FileSystemProtocol {
)
}
func getContentsOfDirectory(_ path: String) throws -> [String] {
// TODO
func getShallowContentsOfDirectory(_ path: String) throws -> [String] {
todo()
return []
}
func getDestinationOfSymlink(_ path: String) throws -> String {
// TODO
todo()
return ""
}
// MARK: - Move & Delete Files

View File

@ -0,0 +1,17 @@
//
// WIP.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 01/11/2022.
// Copyright © 2022 Nico Verbruggen. All rights reserved.
//
import Foundation
func todo(_ context: String = "") {
if !context.isEmpty {
fatalError("To be implemented: \(context)")
}
fatalError("To be implemented")
}

View File

@ -26,7 +26,6 @@ public struct TestableConfiguration: Codable {
ActiveCommand.useTestable(commandOutput)
Log.info("Applying fake scanner...")
ValetScanners.useFake()
Log.separator()
}
func toJson(pretty: Bool = false) -> String {

View File

@ -11,21 +11,40 @@ import Foundation
class TestableFileSystem: FileSystemProtocol {
init(files: [String: FakeFile]) {
self.files = files
for key in files.keys where key.contains("~") {
self.files.renameKey(
fromKey: key,
toKey: key.replacingOccurrences(of: "~", with: self.homeDirectory)
)
}
for file in self.files {
self.createIntermediateDirectories(file.key)
}
}
var files: [String: FakeFile]
private(set) var homeDirectory = "/Users/fake"
// MARK: - Basics
func createDirectory(_ path: String, withIntermediateDirectories: Bool) throws {
let path = path.replacingTildeWithHomeDirectory
if files[path] != nil {
throw TestableFileSystemError.alreadyExists
}
self.createIntermediateDirectories(path)
self.files[path] = .fake(.directory)
}
func writeAtomicallyToFile(_ path: String, content: String) throws {
let path = path.replacingTildeWithHomeDirectory
if files[path] != nil {
throw TestableFileSystemError.alreadyExists
}
@ -34,6 +53,8 @@ class TestableFileSystem: FileSystemProtocol {
}
func getStringFromFile(_ path: String) throws -> String {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
throw TestableFileSystemError.fileMissing
}
@ -41,11 +62,23 @@ class TestableFileSystem: FileSystemProtocol {
return file.content ?? ""
}
func getContentsOfDirectory(_ path: String) throws -> [String] {
// TODO
func getShallowContentsOfDirectory(_ path: String) throws -> [String] {
let path = path.replacingTildeWithHomeDirectory
var seek = path
if !seek.hasSuffix("/") {
seek = "\(seek)/"
}
return self.files.keys
.filter { $0.hasPrefix(seek) }
.map { $0.replacingOccurrences(of: seek, with: "") }
.filter { !$0.contains("/") }
}
func getDestinationOfSymlink(_ path: String) throws -> String {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
throw TestableFileSystemError.fileMissing
}
@ -68,16 +101,22 @@ class TestableFileSystem: FileSystemProtocol {
// MARK: - Move & Delete Files
func move(from path: String, to newPath: String) throws {
let path = path.replacingTildeWithHomeDirectory
let newPath = newPath.replacingTildeWithHomeDirectory
// TODO
}
func remove(_ path: String) throws {
let path = path.replacingTildeWithHomeDirectory
// TODO
}
// MARK: Attributes
func makeExecutable(_ path: String) throws {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
throw TestableFileSystemError.fileMissing
}
@ -88,7 +127,9 @@ class TestableFileSystem: FileSystemProtocol {
// MARK: - Checks
func isExecutableFile(_ path: String) -> Bool {
guard let file = files[path] else {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path.replacingTildeWithHomeDirectory] else {
return false
}
@ -96,7 +137,9 @@ class TestableFileSystem: FileSystemProtocol {
}
func isWriteableFile(_ path: String) -> Bool {
guard let file = files[path] else {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path.replacingTildeWithHomeDirectory] else {
return false
}
@ -104,10 +147,14 @@ class TestableFileSystem: FileSystemProtocol {
}
func anyExists(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
return files.keys.contains(path)
}
func fileExists(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
return false
}
@ -116,6 +163,8 @@ class TestableFileSystem: FileSystemProtocol {
}
func directoryExists(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
return false
}
@ -124,6 +173,8 @@ class TestableFileSystem: FileSystemProtocol {
}
func isSymlink(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
return false
}
@ -132,12 +183,40 @@ class TestableFileSystem: FileSystemProtocol {
}
func isDirectory(_ path: String) -> Bool {
let path = path.replacingTildeWithHomeDirectory
guard let file = files[path] else {
return false
}
return file.type == .directory
}
public func printContents() {
for key in self.files.keys.sorted() {
print("\(key) -> \(self.files[key]!.type)")
}
}
private func createIntermediateDirectories(_ path: String) {
let path = path.replacingTildeWithHomeDirectory
let items = path.components(separatedBy: "/")
var preceding = ""
for item in items {
let key = preceding == "/"
? "/\(item)"
: "\(preceding)/\(item)"
if !self.files.keys.contains(key) {
self.files[key] = .fake(.directory)
}
preceding = key
}
}
}
enum FakeFileType: Codable {

View File

@ -13,7 +13,7 @@ class ValetSiteScanner: SiteScanner {
return paths.map { path in
let entries = try! FileSystem
.getContentsOfDirectory(path)
.getShallowContentsOfDirectory(path)
return entries
.map { self.isSite($0, forPath: path) }
@ -28,7 +28,7 @@ class ValetSiteScanner: SiteScanner {
paths.forEach { path in
let entries = try! FileSystem
.getContentsOfDirectory(path)
.getShallowContentsOfDirectory(path)
return entries.forEach {
if let site = self.resolveSite(path: "\(path)/\($0)") {

View File

@ -26,15 +26,11 @@ class TestableConfigurations {
: .fake(.symlink, "/opt/homebrew/Cellar/php/8.1.10_1"),
"/opt/homebrew/opt/php@8.1/bin/php"
: .fake(.symlink, "/opt/homebrew/Cellar/php/8.1.10_1/bin/php"),
"/opt/homebrew/Cellar/php/8.1.10_1"
: .fake(.directory),
"/opt/homebrew/Cellar/php/8.1.10_1/bin/php"
: .fake(.binary),
"/opt/homebrew/Cellar/php/8.1.10_1/bin/php-config"
: .fake(.binary),
"/Users/user/.config/valet"
: .fake(.directory),
"/Users/user/.config/valet/config.json"
"~/.config/valet/config.json"
: .fake(.text, """
{
"tld": "test",

View File

@ -12,12 +12,12 @@ class TestableFileSystemTest: XCTestCase {
override class func setUp() {
ActiveFileSystem.useTestable([
"/home/user/bin": .fake(.directory),
"/home/user/bin/foo": .fake(.binary),
"/home/user/documents": .fake(.directory),
"/home/user/docs": .fake(.symlink, "/home/user/documents"),
"/home/user/documents/script.sh": .fake(.text, "echo 'cool';"),
"/home/user/documents/nice.txt": .fake(.text, "69"),
"/home/user/documents/script.sh": .fake(.text, "echo 'cool';")
"/home/user/documents/filters/filter1.txt": .fake(.text, "F1"),
"/home/user/documents/filters/filter2.txt": .fake(.text, "F2")
])
}
@ -25,7 +25,11 @@ class TestableFileSystemTest: XCTestCase {
XCTAssertTrue(FileSystem is TestableFileSystem)
}
func test_binary_directory_exists() {
func test_intermediate_directories_are_automatically_created() {
XCTAssertTrue(FileSystem.directoryExists("/"))
XCTAssertTrue(FileSystem.directoryExists("/home"))
XCTAssertTrue(FileSystem.directoryExists("/home/user"))
XCTAssertTrue(FileSystem.directoryExists("/home/user/documents"))
XCTAssertTrue(FileSystem.directoryExists("/home/user/bin"))
}
@ -53,9 +57,37 @@ class TestableFileSystemTest: XCTestCase {
withIntermediateDirectories: true
)
XCTAssertTrue(FileSystem.anyExists("/home/nico/phpmon/config"))
XCTAssertTrue(FileSystem
.anyExists("/home/nico/phpmon/config"))
XCTAssertTrue(FileSystem.directoryExists("/home/nico/phpmon/config"))
}
func test_can_create_nested_directories() throws {
try FileSystem.createDirectory(
"/home/user/thing/epic/nested/directories",
withIntermediateDirectories: true
)
XCTAssertTrue(FileSystem.directoryExists("/"))
XCTAssertTrue(FileSystem.directoryExists("/home"))
XCTAssertTrue(FileSystem.directoryExists("/home/user"))
XCTAssertTrue(FileSystem.directoryExists("/home/user/thing"))
XCTAssertTrue(FileSystem.directoryExists("/home/user/thing/epic/nested"))
XCTAssertTrue(FileSystem.directoryExists("/home/user/thing/epic/nested/directories"))
}
func test_can_list_directory_contents() throws {
let contents = try! FileSystem.getShallowContentsOfDirectory("/home/user/documents")
XCTAssertEqual(
contents.sorted(),
[
"script.sh",
"nice.txt",
"filters"
].sorted()
)
}
// TODO: Implement and test the remove() and move() methods and reorganize method order
}