From 5caca85d7a72d7bd7c2401fec9f82df4c932b9a1 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Tue, 1 Nov 2022 16:47:45 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=8C=20Fake=20FS:=20~=20and=20intermedi?= =?UTF-8?q?ate=20directories?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 20 +++++ .../xcschemes/PHP Monitor DEV.xcscheme | 2 +- phpmon/Common/Core/Paths.swift | 10 ++- .../Extensions/DictionaryExtension.swift | 17 ++++ .../Common/Filesystem/ActiveFileSystem.swift | 1 + .../Filesystem/FileSystemProtocol.swift | 2 +- phpmon/Common/Filesystem/RealFileSystem.swift | 8 +- phpmon/Common/Helpers/WIP.swift | 17 ++++ .../Testables/TestableConfiguration.swift | 1 - .../Common/Testables/TestableFileSystem.swift | 87 ++++++++++++++++++- .../Sites/SiteScanner/ValetSiteScanner.swift | 4 +- tests/Shared/TestableConfigurations.swift | 6 +- .../Filesystem/TestableFileSystemTest.swift | 42 +++++++-- 13 files changed, 192 insertions(+), 25 deletions(-) create mode 100644 phpmon/Common/Extensions/DictionaryExtension.swift create mode 100644 phpmon/Common/Helpers/WIP.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 7571def..961c03f 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -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 = ""; }; C4D3660A29113F20006BD146 /* System.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = System.swift; sourceTree = ""; }; C4D3660F291140BE006BD146 /* TestableFileSystemTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableFileSystemTest.swift; sourceTree = ""; }; + C4D36614291160A1006BD146 /* WIP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WIP.swift; sourceTree = ""; }; + C4D36619291173EA006BD146 /* DictionaryExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DictionaryExtension.swift; sourceTree = ""; }; C4D5CFC927E0F9CD00035329 /* NginxConfigurationFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NginxConfigurationFile.swift; sourceTree = ""; }; C4D8016522B1584700C6DA1B /* Startup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Startup.swift; sourceTree = ""; }; C4D89BC52783C99400A02B68 /* ComposerJson.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerJson.swift; sourceTree = ""; }; @@ -1364,6 +1374,7 @@ C4CCBA6B275C567B008C7055 /* PMWindowController.swift */, C4B5635D276AB09000F12CCB /* VersionExtractor.swift */, C4D3660A29113F20006BD146 /* System.swift */, + C4D36614291160A1006BD146 /* WIP.swift */, ); path = Helpers; sourceTree = ""; @@ -1704,6 +1715,7 @@ C4EB53E628553117006F9937 /* ArrayExtension.swift */, C44B3A4528E5C70100718CB1 /* TimeIntervalExtension.swift */, C4E2E84928FC1E70003B070C /* DataExtension.swift */, + C4D36619291173EA006BD146 /* DictionaryExtension.swift */, ); path = Extensions; sourceTree = ""; @@ -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 */, diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index ff88595..0b2dcd1 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -80,7 +80,7 @@ + isEnabled = "YES"> String - func getContentsOfDirectory(_ path: String) throws -> [String] + func getShallowContentsOfDirectory(_ path: String) throws -> [String] func getDestinationOfSymlink(_ path: String) throws -> String diff --git a/phpmon/Common/Filesystem/RealFileSystem.swift b/phpmon/Common/Filesystem/RealFileSystem.swift index f54c861..e3ed7cf 100644 --- a/phpmon/Common/Filesystem/RealFileSystem.swift +++ b/phpmon/Common/Filesystem/RealFileSystem.swift @@ -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 diff --git a/phpmon/Common/Helpers/WIP.swift b/phpmon/Common/Helpers/WIP.swift new file mode 100644 index 0000000..dbe89f4 --- /dev/null +++ b/phpmon/Common/Helpers/WIP.swift @@ -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") +} diff --git a/phpmon/Common/Testables/TestableConfiguration.swift b/phpmon/Common/Testables/TestableConfiguration.swift index 60a1f21..ea4280d 100644 --- a/phpmon/Common/Testables/TestableConfiguration.swift +++ b/phpmon/Common/Testables/TestableConfiguration.swift @@ -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 { diff --git a/phpmon/Common/Testables/TestableFileSystem.swift b/phpmon/Common/Testables/TestableFileSystem.swift index 4f2b1b9..ccddc7a 100644 --- a/phpmon/Common/Testables/TestableFileSystem.swift +++ b/phpmon/Common/Testables/TestableFileSystem.swift @@ -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 { diff --git a/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/ValetSiteScanner.swift b/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/ValetSiteScanner.swift index 801f89d..6fe84d3 100644 --- a/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/ValetSiteScanner.swift +++ b/phpmon/Domain/Integrations/Valet/Sites/SiteScanner/ValetSiteScanner.swift @@ -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)") { diff --git a/tests/Shared/TestableConfigurations.swift b/tests/Shared/TestableConfigurations.swift index 28374d1..4186132 100644 --- a/tests/Shared/TestableConfigurations.swift +++ b/tests/Shared/TestableConfigurations.swift @@ -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", diff --git a/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift b/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift index 1329933..4e2b8ec 100644 --- a/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift +++ b/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift @@ -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 }