diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 0373001..7571def 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -72,7 +72,7 @@ C40FE738282ABA4F00A302C2 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE736282ABA4F00A302C2 /* AppVersion.swift */; }; C40FE73B282ABB2E00A302C2 /* AppVersionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40FE739282ABB2E00A302C2 /* AppVersionTest.swift */; }; C412E5FC25700D5300A1FB67 /* HomebrewPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */; }; - C413E43528DA3EB100AE33C7 /* FakeShellTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C413E43428DA3EB100AE33C7 /* FakeShellTest.swift */; }; + C413E43528DA3EB100AE33C7 /* TestableShellTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C413E43428DA3EB100AE33C7 /* TestableShellTest.swift */; }; C415937F27A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */; }; C415938027A1B54F00D2E1B7 /* PhpFrameworks.swift in Sources */ = {isa = PBXBuildFile; fileRef = C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */; }; C4159AF728E4D40400545349 /* SystemShellTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4159AF628E4D40400545349 /* SystemShellTest.swift */; }; @@ -569,6 +569,11 @@ C4D366072911331E006BD146 /* EmptyProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D366052911331E006BD146 /* EmptyProxyScanner.swift */; }; C4D366082911331E006BD146 /* EmptyProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D366052911331E006BD146 /* EmptyProxyScanner.swift */; }; C4D366092911331E006BD146 /* EmptyProxyScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D366052911331E006BD146 /* EmptyProxyScanner.swift */; }; + C4D3660B29113F20006BD146 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D3660A29113F20006BD146 /* System.swift */; }; + C4D3660C29113F20006BD146 /* System.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D3660A29113F20006BD146 /* System.swift */; }; + 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 */; }; 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 */; }; @@ -725,7 +730,7 @@ C40FE736282ABA4F00A302C2 /* AppVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersion.swift; sourceTree = ""; }; C40FE739282ABB2E00A302C2 /* AppVersionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionTest.swift; sourceTree = ""; }; C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewPackage.swift; sourceTree = ""; }; - C413E43428DA3EB100AE33C7 /* FakeShellTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeShellTest.swift; sourceTree = ""; }; + C413E43428DA3EB100AE33C7 /* TestableShellTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestableShellTest.swift; sourceTree = ""; }; C415937E27A1B54F00D2E1B7 /* PhpFrameworks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhpFrameworks.swift; sourceTree = ""; }; C4159AF628E4D40400545349 /* SystemShellTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemShellTest.swift; sourceTree = ""; }; C415D3B62770F294005EF286 /* Actions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = ""; }; @@ -851,6 +856,8 @@ C4CE3BB927B31F670086CA49 /* ComposerWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerWindow.swift; sourceTree = ""; }; C4D36600291132B7006BD146 /* ValetScanners.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetScanners.swift; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; @@ -1069,7 +1076,7 @@ C413E43328DA3E8F00AE33C7 /* Shell */ = { isa = PBXGroup; children = ( - C413E43428DA3EB100AE33C7 /* FakeShellTest.swift */, + C413E43428DA3EB100AE33C7 /* TestableShellTest.swift */, C4159AF628E4D40400545349 /* SystemShellTest.swift */, ); path = Shell; @@ -1291,6 +1298,7 @@ C471E6DA28F9AFCB0021E251 /* Filesystem */ = { isa = PBXGroup; children = ( + C4D3660F291140BE006BD146 /* TestableFileSystemTest.swift */, ); path = Filesystem; sourceTree = ""; @@ -1355,6 +1363,7 @@ C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */, C4CCBA6B275C567B008C7055 /* PMWindowController.swift */, C4B5635D276AB09000F12CCB /* VersionExtractor.swift */, + C4D3660A29113F20006BD146 /* System.swift */, ); path = Helpers; sourceTree = ""; @@ -2038,6 +2047,7 @@ C4B6091D2853AB9700C95265 /* ServicesView.swift in Sources */, C40508B128ADAB44008FAC1F /* NSMenuItemExtension.swift in Sources */, C4B97B7B275CF20A003F3378 /* App+GlobalHotkey.swift in Sources */, + C4D3660B29113F20006BD146 /* System.swift in Sources */, C4D36601291132B7006BD146 /* ValetScanners.swift in Sources */, C4EED88927A48778006D7272 /* InterAppHandler.swift in Sources */, C40C7F1E2772136000DDDCDC /* PhpEnv.swift in Sources */, @@ -2146,6 +2156,7 @@ C471E86A28F9BB650021E251 /* PrefsVC.swift in Sources */, C471E86B28F9BB650021E251 /* PreferenceName.swift in Sources */, C471E86C28F9BB650021E251 /* Preferences.swift in Sources */, + C4D3660D29113F20006BD146 /* System.swift in Sources */, C471E86D28F9BB650021E251 /* CustomPrefs.swift in Sources */, C4E2E84C28FC1E70003B070C /* DataExtension.swift in Sources */, C471E86E28F9BB650021E251 /* MenuBarIcons.swift in Sources */, @@ -2355,6 +2366,7 @@ C471E82128F9BB2E0021E251 /* PhpFrameworks.swift in Sources */, C471E7EF28F9BAC30021E251 /* Actions.swift in Sources */, C471E82228F9BB2E0021E251 /* ComposerWindow.swift in Sources */, + C4D3660E29113F20006BD146 /* System.swift in Sources */, C471E80428F9BAD40021E251 /* PhpExtension.swift in Sources */, C471E7F728F9BACB0021E251 /* PhpSwitcher.swift in Sources */, C471E82C28F9BB340021E251 /* DomainListable.swift in Sources */, @@ -2401,7 +2413,7 @@ C41CA5EE2774F8EE00A2C80E /* DomainListVC+Actions.swift in Sources */, C4FACE81288F1C0D00FC478F /* PreferencesWindowController+Hotkey.swift in Sources */, 54D9E0B727E4F51E003B9AD9 /* HotKey.swift in Sources */, - C413E43528DA3EB100AE33C7 /* FakeShellTest.swift in Sources */, + C413E43528DA3EB100AE33C7 /* TestableShellTest.swift in Sources */, C4205A7F27F4D21800191A39 /* ValetProxy.swift in Sources */, C42F26742805B4B400938AC7 /* DomainListable.swift in Sources */, C46EBC4528DB95F0007ACC74 /* ShellProtocol.swift in Sources */, @@ -2505,7 +2517,9 @@ C4F7809C25D80344000DBC97 /* CommandTest.swift in Sources */, C44CCD4127AFE2FC00CE40E5 /* AlertableError.swift in Sources */, C4CDA894288F1A71007CE25F /* Keys.swift in Sources */, + C4D3660C29113F20006BD146 /* System.swift in Sources */, C4D936CA27E3EB6100BD69FE /* PhpHelper.swift in Sources */, + C4D36611291140BE006BD146 /* TestableFileSystemTest.swift in Sources */, C4E2E84B28FC1E70003B070C /* DataExtension.swift in Sources */, C449B4F127EE7FC200C47E8A /* DomainListNameCell.swift in Sources */, C4F780BA25D80B62000DBC97 /* AppDelegate.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 0b2dcd1..ff88595 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 = "NO"> String + + // MARK: - Move & Delete Files + + func move(from path: String, to newPath: String) throws + + func remove(_ path: String) throws + + // MARK: — Attributes + + func makeExecutable(_ path: String) throws + + // MARK: - Checks + func isExecutableFile(_ path: String) -> Bool - /** - Checks if a file or directory exists at the provided path. - */ - func exists(_ path: String) -> Bool + func isWriteableFile(_ path: String) -> Bool + + func anyExists(_ path: String) -> Bool - /** - Checks if a file exists at the provided path. - */ func fileExists(_ path: String) -> Bool - /** - Checks if a directory exists at the provided path. - */ func directoryExists(_ path: String) -> Bool - /** - Checks if a given file is a symbolic link. - */ func fileIsSymlink(_ path: String) -> Bool } diff --git a/phpmon/Common/Filesystem/RealFileSystem.swift b/phpmon/Common/Filesystem/RealFileSystem.swift index 601f92b..6d6262f 100644 --- a/phpmon/Common/Filesystem/RealFileSystem.swift +++ b/phpmon/Common/Filesystem/RealFileSystem.swift @@ -15,27 +15,67 @@ extension String { } class RealFileSystem: FileSystemProtocol { - /** - Checks if a given path is a file *and* executable. - */ + + // MARK: - Basics + + func createDirectory(_ path: String, withIntermediateDirectories: Bool) { + try! FileManager.default.createDirectory( + atPath: path.replacingTildeWithHomeDirectory, + withIntermediateDirectories: withIntermediateDirectories + ) + } + + func writeAtomicallyToFile(_ path: String, content: String) throws { + try content.write( + to: URL(fileURLWithPath: path.replacingTildeWithHomeDirectory), + atomically: true, + encoding: String.Encoding.utf8 + ) + } + + func readStringFromFile(_ path: String) throws -> String { + return try String( + contentsOf: URL(fileURLWithPath: path.replacingTildeWithHomeDirectory), + encoding: .utf8 + ) + } + + // MARK: - Move & Delete Files + + func move(from path: String, to newPath: String) throws { + // TODO + } + + func remove(_ path: String) throws { + // TODO + } + + // MARK: — FS Attributes + + func makeExecutable(_ path: String) throws { + system("chmod +x \(path.replacingTildeWithHomeDirectory)") + } + + // MARK: - Checks + func isExecutableFile(_ path: String) -> Bool { return FileManager.default.isExecutableFile( atPath: path.replacingTildeWithHomeDirectory ) } - /** - Checks if a file or directory exists at the provided path. - */ - func exists(_ path: String) -> Bool { + func isWriteableFile(_ path: String) -> Bool { + return FileManager.default.isWritableFile( + atPath: path.replacingTildeWithHomeDirectory + ) + } + + func anyExists(_ path: String) -> Bool { return FileManager.default.fileExists( atPath: path.replacingTildeWithHomeDirectory ) } - /** - Checks if a file exists at the provided path. - */ func fileExists(_ path: String) -> Bool { var isDirectory: ObjCBool = true let exists = FileManager.default.fileExists( @@ -46,9 +86,6 @@ class RealFileSystem: FileSystemProtocol { return exists && !isDirectory.boolValue } - /** - Checks if a directory exists at the provided path. - */ func directoryExists(_ path: String) -> Bool { var isDirectory: ObjCBool = true let exists = FileManager.default.fileExists( @@ -59,9 +96,6 @@ class RealFileSystem: FileSystemProtocol { return exists && isDirectory.boolValue } - /** - Checks if a given file is a symbolic link. - */ func fileIsSymlink(_ path: String) -> Bool { do { let attribs = try FileManager.default.attributesOfItem(atPath: path) diff --git a/phpmon/Common/Helpers/System.swift b/phpmon/Common/Helpers/System.swift new file mode 100644 index 0000000..9b8d000 --- /dev/null +++ b/phpmon/Common/Helpers/System.swift @@ -0,0 +1,18 @@ +// +// System.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 01/11/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import Foundation + +public func system(_ command: String) { + let argsArray = command.split(separator: " ").map { String($0) } + guard argsArray.isEmpty else { return } + let command = strdup(argsArray.first!) + let args = argsArray.map { strdup($0) } + [nil] + posix_spawn(nil, command, nil, nil, args, nil) + return +} diff --git a/phpmon/Common/PHP/PHP Version/PhpHelper.swift b/phpmon/Common/PHP/PHP Version/PhpHelper.swift index 28a200b..4e1ce7b 100644 --- a/phpmon/Common/PHP/PHP Version/PhpHelper.swift +++ b/phpmon/Common/PHP/PHP Version/PhpHelper.swift @@ -23,12 +23,15 @@ class PhpHelper { let inPath = Shell.PATH.contains("\(Paths.homePath)/.config/phpmon/bin") // Check if we can create symlinks (`/usr/local/bin` must be writable) - let canWriteSymlinks = FileManager.default.isWritableFile(atPath: "/usr/local/bin/") + let canWriteSymlinks = FileSystem.isWriteableFile("/usr/local/bin/") Task { // Create the appropriate folders and check if the files exist do { if !FileSystem.directoryExists("~/.config/phpmon/bin") { - await Shell.quiet("mkdir -p ~/.config/phpmon/bin") + try FileSystem.createDirectory( + "~/.config/phpmon/bin", + withIntermediateDirectories: true + ) } if FileSystem.fileExists(destination) { @@ -56,17 +59,10 @@ class PhpHelper { export PATH=\(path):$PATH """ - // Write to the destination - // TODO: Use FileSystem abstraction - try script.write( - to: URL(fileURLWithPath: destination), - atomically: true, - encoding: String.Encoding.utf8 - ) + try FileSystem.writeAtomicallyToFile(destination, content: script) - // Make sure the file is executable if !FileSystem.isExecutableFile(destination) { - await Shell.quiet("chmod +x \(destination)") + try FileSystem.makeExecutable(destination) } // Create a symlink if the folder is not in the PATH diff --git a/phpmon/Common/PHP/Switcher/InternalSwitcher.swift b/phpmon/Common/PHP/Switcher/InternalSwitcher.swift index 372c0cb..f6d9374 100644 --- a/phpmon/Common/PHP/Switcher/InternalSwitcher.swift +++ b/phpmon/Common/PHP/Switcher/InternalSwitcher.swift @@ -74,22 +74,22 @@ class InternalSwitcher: PhpSwitcher { func requiresDisablingOfDefaultPhpFpmPool(_ version: String) -> Bool { let pool = "\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf" - return FileManager.default.fileExists(atPath: pool) + return FileSystem.fileExists(pool) } func disableDefaultPhpFpmPool(_ version: String) async { let pool = "\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf" - if FileManager.default.fileExists(atPath: pool) { + if FileSystem.fileExists(pool) { Log.info("A default `www.conf` file was found in the php-fpm.d directory for PHP \(version).") - let existing = URL(string: "file://\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf")! - let new = URL(string: "file://\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf.disabled-by-phpmon")! + let existing = "\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf" + let new = "\(Paths.etcPath)/php/\(version)/php-fpm.d/www.conf.disabled-by-phpmon" do { - if FileManager.default.fileExists(atPath: new.path) { + if FileSystem.fileExists(new) { Log.info("A moved `www.conf.disabled-by-phpmon` file was found for PHP \(version), " + "cleaning up so the newer `www.conf` can be moved again.") - try FileManager.default.removeItem(at: new) + try FileSystem.remove(new) } - try FileManager.default.moveItem(at: existing, to: new) + try FileSystem.move(from: existing, to: new) Log.info("Success: A default `www.conf` file was disabled for PHP \(version).") } catch { Log.err(error) diff --git a/phpmon/Common/Testables/TestableFileSystem.swift b/phpmon/Common/Testables/TestableFileSystem.swift index ca6f41b..b3f425d 100644 --- a/phpmon/Common/Testables/TestableFileSystem.swift +++ b/phpmon/Common/Testables/TestableFileSystem.swift @@ -15,6 +15,54 @@ class TestableFileSystem: FileSystemProtocol { var files: [String: FakeFile] + // MARK: - Basics + + func createDirectory(_ path: String, withIntermediateDirectories: Bool) throws { + if files[path] != nil { + throw TestableFileSystemError.alreadyExists + } + + self.files[path] = .fake(.directory) + } + + func writeAtomicallyToFile(_ path: String, content: String) throws { + if files[path] != nil { + throw TestableFileSystemError.alreadyExists + } + + self.files[path] = .fake(.text, content) + } + + func readStringFromFile(_ path: String) throws -> String { + guard let file = files[path] else { + throw TestableFileSystemError.fileMissing + } + + return file.content ?? "" + } + + // MARK: - Move & Delete Files + + func move(from path: String, to newPath: String) throws { + // TODO + } + + func remove(_ path: String) throws { + // TODO + } + + // MARK: — Attributes + + func makeExecutable(_ path: String) throws { + guard let file = files[path] else { + throw TestableFileSystemError.fileMissing + } + + file.type = .binary + } + + // MARK: - Checks + func isExecutableFile(_ path: String) -> Bool { guard let file = files[path] else { return false @@ -23,7 +71,15 @@ class TestableFileSystem: FileSystemProtocol { return file.type == .binary } - func exists(_ path: String) -> Bool { + func isWriteableFile(_ path: String) -> Bool { + guard let file = files[path] else { + return false + } + + return !file.readOnly + } + + func anyExists(_ path: String) -> Bool { return files.keys.contains(path) } @@ -56,11 +112,31 @@ enum FakeFileType: Codable { case binary, text, directory, symlink } -struct FakeFile: Codable { +class FakeFile: Codable { var type: FakeFileType var content: String? + var readOnly: Bool = false - public static func fake(_ type: FakeFileType, _ content: String? = nil) -> FakeFile { - return FakeFile(type: type, content: content) + init(type: FakeFileType, content: String?, readOnly: Bool = false) { + self.type = type + self.content = content + self.readOnly = readOnly + } + + public static func fake( + _ type: FakeFileType, + _ content: String? = nil, + readOnly: Bool = false + ) -> FakeFile { + return FakeFile( + type: type, + content: content, + readOnly: readOnly + ) } } + +enum TestableFileSystemError: Error { + case fileMissing + case alreadyExists +} diff --git a/phpmon/Domain/DomainList/AddSiteVC.swift b/phpmon/Domain/DomainList/AddSiteVC.swift index a427613..17fd285 100644 --- a/phpmon/Domain/DomainList/AddSiteVC.swift +++ b/phpmon/Domain/DomainList/AddSiteVC.swift @@ -55,7 +55,7 @@ class AddSiteVC: NSViewController, NSTextFieldDelegate { let path = pathControl.url!.path let name = inputDomainName.stringValue - if !FileSystem.exists(path) { + if !FileSystem.anyExists(path) { Alert.confirm( onWindow: view.window!, messageText: "domain_list.alert.folder_missing.title".localized, diff --git a/phpmon/Domain/Integrations/Composer/PhpFrameworks.swift b/phpmon/Domain/Integrations/Composer/PhpFrameworks.swift index 2de9176..dd66bdc 100644 --- a/phpmon/Domain/Integrations/Composer/PhpFrameworks.swift +++ b/phpmon/Domain/Integrations/Composer/PhpFrameworks.swift @@ -71,7 +71,7 @@ struct PhpFrameworks { public static func detectFallbackDependency(_ basePath: String) -> String? { for entry in Self.FileMapping { let found = entry.value - .map { path in return FileSystem.exists(basePath + path) } + .map { path in return FileSystem.anyExists(basePath + path) } .contains(true) if found { diff --git a/phpmon/Domain/Integrations/Valet/Valet.swift b/phpmon/Domain/Integrations/Valet/Valet.swift index 5b11982..2bdc6fe 100644 --- a/phpmon/Domain/Integrations/Valet/Valet.swift +++ b/phpmon/Domain/Integrations/Valet/Valet.swift @@ -98,13 +98,10 @@ class Valet { If the JSON is invalid when the app launches, an alert will be presented, however. */ public func loadConfiguration() { - let file = FileManager.default.homeDirectoryForCurrentUser - .appendingPathComponent(".config/valet/config.json") - do { config = try JSONDecoder().decode( Valet.Configuration.self, - from: try String(contentsOf: file, encoding: .utf8).data(using: .utf8)! + from: FileSystem.readStringFromFile("~/.config/valet/config.json").data(using: .utf8)! ) } catch { Log.err(error) diff --git a/phpmon/Domain/Watcher/PhpConfigWatcher.swift b/phpmon/Domain/Watcher/PhpConfigWatcher.swift index 37d006b..fc00cc3 100644 --- a/phpmon/Domain/Watcher/PhpConfigWatcher.swift +++ b/phpmon/Domain/Watcher/PhpConfigWatcher.swift @@ -51,7 +51,7 @@ class PhpConfigWatcher { eventMask: DispatchSource.FileSystemEvent, behaviour: FSWatcherBehaviour = .reloadsMenu ) { - if !FileSystem.exists(url.path) { + if !FileSystem.anyExists(url.path) { Log.warn("No watcher was created for \(url.path) because the requested file does not exist.") return } diff --git a/tests/Shared/TestableConfigurations.swift b/tests/Shared/TestableConfigurations.swift index 05f00e4..28374d1 100644 --- a/tests/Shared/TestableConfigurations.swift +++ b/tests/Shared/TestableConfigurations.swift @@ -14,6 +14,8 @@ class TestableConfigurations { return TestableConfiguration( architecture: "arm64", filesystem: [ + "/usr/local/bin/" + : .fake(.directory, readOnly: true), "/opt/homebrew/bin/brew" : .fake(.binary), "/opt/homebrew/bin/php" @@ -30,10 +32,21 @@ class TestableConfigurations { : .fake(.binary), "/opt/homebrew/Cellar/php/8.1.10_1/bin/php-config" : .fake(.binary), - "~/.config/valet" + "/Users/user/.config/valet" : .fake(.directory), + "/Users/user/.config/valet/config.json" + : .fake(.text, """ + { + "tld": "test", + "paths": [ + "/Users/user/.config/valet/Sites", + "/Users/user/Sites" + ], + "loopback": "127.0.0.1" + } + """), "/opt/homebrew/etc/php/8.1/php-fpm.d/valet-fpm.conf" - : .fake(.text) + : .fake(.text), ], shellOutput: [ "sysctl -n sysctl.proc_translated" diff --git a/tests/Shared/Utility.swift b/tests/Shared/Utility.swift index abf2b78..3caa4ec 100644 --- a/tests/Shared/Utility.swift +++ b/tests/Shared/Utility.swift @@ -1,6 +1,6 @@ // // Utility.swift -// phpmon-tests +// PHP Monitor // // Created by Nico Verbruggen on 14/02/2021. // Copyright © 2022 Nico Verbruggen. All rights reserved. diff --git a/tests/unit/Commands/CommandTest.swift b/tests/unit/Commands/CommandTest.swift index d5ea2b6..1241bf2 100644 --- a/tests/unit/Commands/CommandTest.swift +++ b/tests/unit/Commands/CommandTest.swift @@ -1,6 +1,6 @@ // // CommandTest.swift -// phpmon-tests +// PHP Monitor // // Created by Nico Verbruggen on 13/02/2021. // Copyright © 2022 Nico Verbruggen. All rights reserved. diff --git a/tests/unit/Parsers/HomebrewPackageTest.swift b/tests/unit/Parsers/HomebrewPackageTest.swift index f6a30f7..81c9718 100644 --- a/tests/unit/Parsers/HomebrewPackageTest.swift +++ b/tests/unit/Parsers/HomebrewPackageTest.swift @@ -1,6 +1,6 @@ // // BrewJsonParserTest.swift -// phpmon-tests +// PHP Monitor // // Created by Nico Verbruggen on 14/02/2021. // Copyright © 2022 Nico Verbruggen. All rights reserved. diff --git a/tests/unit/Parsers/NginxConfigurationTest.swift b/tests/unit/Parsers/NginxConfigurationTest.swift index 07fa2a3..b09175c 100644 --- a/tests/unit/Parsers/NginxConfigurationTest.swift +++ b/tests/unit/Parsers/NginxConfigurationTest.swift @@ -1,6 +1,6 @@ // // NginxConfigurationTest.swift -// phpmon-tests +// PHP Monitor // // Created by Nico Verbruggen on 29/11/2021. // Copyright © 2022 Nico Verbruggen. All rights reserved. diff --git a/tests/unit/Parsers/PhpExtensionTest.swift b/tests/unit/Parsers/PhpExtensionTest.swift index 0bbebc2..2647559 100644 --- a/tests/unit/Parsers/PhpExtensionTest.swift +++ b/tests/unit/Parsers/PhpExtensionTest.swift @@ -1,6 +1,6 @@ // // ExtensionParserTest.swift -// phpmon-tests +// PHP Monitor // // Created by Nico Verbruggen on 13/02/2021. // Copyright © 2022 Nico Verbruggen. All rights reserved. diff --git a/tests/unit/Parsers/ValetConfigurationTest.swift b/tests/unit/Parsers/ValetConfigurationTest.swift index e640177..5b57fe9 100644 --- a/tests/unit/Parsers/ValetConfigurationTest.swift +++ b/tests/unit/Parsers/ValetConfigurationTest.swift @@ -1,6 +1,6 @@ // // ValetConfigParserTest.swift -// phpmon-tests +// PHP Monitor // // Created by Nico Verbruggen on 29/11/2021. // Copyright © 2022 Nico Verbruggen. All rights reserved. diff --git a/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift b/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift new file mode 100644 index 0000000..1329933 --- /dev/null +++ b/tests/unit/Testables/Filesystem/TestableFileSystemTest.swift @@ -0,0 +1,61 @@ +// +// TestableFileSystemTest.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 01/11/2022. +// Copyright © 2022 Nico Verbruggen. All rights reserved. +// + +import XCTest + +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/nice.txt": .fake(.text, "69"), + "/home/user/documents/script.sh": .fake(.text, "echo 'cool';") + ]) + } + + func test_testable_fs_is_in_use() { + XCTAssertTrue(FileSystem is TestableFileSystem) + } + + func test_binary_directory_exists() { + XCTAssertTrue(FileSystem.directoryExists("/home/user/bin")) + } + + func test_binary_directory_is_writable() { + XCTAssertTrue(FileSystem.isWriteableFile("/home/user/bin")) + } + + func test_binary_exists() { + XCTAssertTrue(FileSystem.isExecutableFile("/home/user/bin/foo")) + } + + func test_can_write_text_to_executable() throws { + try! FileSystem.writeAtomicallyToFile("/home/user/bin/bar", content: "bar bar bar!") + + XCTAssertFalse(FileSystem.isExecutableFile("/home/user/bin/bar")) + + try! FileSystem.makeExecutable("/home/user/bin/bar") + + XCTAssertTrue(FileSystem.isExecutableFile("/home/user/bin/bar")) + } + + func test_can_create_directory() throws { + try! FileSystem.createDirectory( + "/home/nico/phpmon/config", + withIntermediateDirectories: true + ) + + XCTAssertTrue(FileSystem.anyExists("/home/nico/phpmon/config")) + XCTAssertTrue(FileSystem.directoryExists("/home/nico/phpmon/config")) + } + + // TODO: Implement and test the remove() and move() methods and reorganize method order +} diff --git a/tests/unit/Testables/Shell/SystemShellTest.swift b/tests/unit/Testables/Shell/SystemShellTest.swift index 7771285..467fc9a 100644 --- a/tests/unit/Testables/Shell/SystemShellTest.swift +++ b/tests/unit/Testables/Shell/SystemShellTest.swift @@ -1,6 +1,6 @@ // // SystemShellTest.swift -// phpmon-tests +// PHP Monitor // // Created by Nico Verbruggen on 28/09/2022. // Copyright © 2022 Nico Verbruggen. All rights reserved. diff --git a/tests/unit/Testables/Shell/FakeShellTest.swift b/tests/unit/Testables/Shell/TestableShellTest.swift similarity index 92% rename from tests/unit/Testables/Shell/FakeShellTest.swift rename to tests/unit/Testables/Shell/TestableShellTest.swift index 16cfc34..d2a819a 100644 --- a/tests/unit/Testables/Shell/FakeShellTest.swift +++ b/tests/unit/Testables/Shell/TestableShellTest.swift @@ -1,6 +1,6 @@ // -// ShellTest.swift -// phpmon-tests +// TestableShellTest.swift +// PHP Monitor // // Created by Nico Verbruggen on 20/09/2022. // Copyright © 2022 Nico Verbruggen. All rights reserved. @@ -8,7 +8,7 @@ import XCTest -class FakeShellTest: XCTestCase { +class TestableShellTest: XCTestCase { func test_fake_shell_output_can_be_declared() async { let greeting = BatchFakeShellOutput(items: [ .instant("Hello world\n"), diff --git a/tests/unit/Versions/AppUpdaterCheckTest.swift b/tests/unit/Versions/AppUpdaterCheckTest.swift index 11215fd..c70d5a2 100644 --- a/tests/unit/Versions/AppUpdaterCheckTest.swift +++ b/tests/unit/Versions/AppUpdaterCheckTest.swift @@ -1,6 +1,6 @@ // // AppUpdaterCheckTest.swift -// phpmon-tests +// PHP Monitor // // Created by Nico Verbruggen on 10/05/2022. // Copyright © 2022 Nico Verbruggen. All rights reserved. diff --git a/tests/unit/Versions/PhpVersionDetectionTest.swift b/tests/unit/Versions/PhpVersionDetectionTest.swift index 64541b0..d9f636e 100644 --- a/tests/unit/Versions/PhpVersionDetectionTest.swift +++ b/tests/unit/Versions/PhpVersionDetectionTest.swift @@ -1,6 +1,6 @@ // // PhpVersionDetectionTest.swift -// phpmon-tests +// PHP Monitor // // Created by Nico Verbruggen on 01/04/2021. // Copyright © 2022 Nico Verbruggen. All rights reserved. diff --git a/tests/unit/Versions/ValetVersionExtractorTest.swift b/tests/unit/Versions/ValetVersionExtractorTest.swift index e1499f3..9e7673e 100644 --- a/tests/unit/Versions/ValetVersionExtractorTest.swift +++ b/tests/unit/Versions/ValetVersionExtractorTest.swift @@ -1,6 +1,6 @@ // // ValetTest.swift -// phpmon-tests +// PHP Monitor // // Created by Nico Verbruggen on 29/11/2021. // Copyright © 2022 Nico Verbruggen. All rights reserved. diff --git a/tests/unit/Versions/VersionExtractorTest.swift b/tests/unit/Versions/VersionExtractorTest.swift index 3501af1..6b44657 100644 --- a/tests/unit/Versions/VersionExtractorTest.swift +++ b/tests/unit/Versions/VersionExtractorTest.swift @@ -1,6 +1,6 @@ // // VersionExtractorTest.swift -// phpmon-tests +// PHP Monitor // // Created by Nico Verbruggen on 16/12/2021. // Copyright © 2022 Nico Verbruggen. All rights reserved.