From 127d5f44948d621387278c6425764353151ae4c6 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Fri, 3 Mar 2023 23:11:40 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=85=20Improve=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 4 + phpmon/Common/PHP/PHP Version/PhpHelper.swift | 18 ++-- .../Testables/TestableConfiguration.swift | 84 +++++++++++++++++++ tests/Shared/TestableConfigurations.swift | 39 ++------- tests/ui/MainMenuTest.swift | 50 +++++++++++ tests/ui/StartupTest.swift | 31 +------ tests/ui/UITestCase.swift | 9 +- 7 files changed, 166 insertions(+), 69 deletions(-) create mode 100644 tests/ui/MainMenuTest.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 985dacc..d2060d1 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -587,6 +587,7 @@ C4C8E819276F54D8003AC782 /* App+ConfigWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */; }; C4C8E81B276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */; }; C4C8E81C276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */; }; + C4CB250529B28BB800CA4492 /* MainMenuTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CB250429B28BB800CA4492 /* MainMenuTest.swift */; }; C4CB6E65292C362C002E9027 /* Homebrew.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CB6E64292C362C002E9027 /* Homebrew.swift */; }; C4CB6E66292C362C002E9027 /* Homebrew.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CB6E64292C362C002E9027 /* Homebrew.swift */; }; C4CB6E67292C362C002E9027 /* Homebrew.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CB6E64292C362C002E9027 /* Homebrew.swift */; }; @@ -915,6 +916,7 @@ C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveFileSystem.swift; sourceTree = ""; }; C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "App+ConfigWatch.swift"; sourceTree = ""; }; C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpConfigWatcher.swift; sourceTree = ""; }; + C4CB250429B28BB800CA4492 /* MainMenuTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenuTest.swift; sourceTree = ""; }; C4CB6E64292C362C002E9027 /* Homebrew.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Homebrew.swift; sourceTree = ""; }; C4CCBA6B275C567B008C7055 /* PMWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PMWindowController.swift; sourceTree = ""; }; C4CDA892288F1A71007CE25F /* Keys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keys.swift; sourceTree = ""; }; @@ -1432,6 +1434,7 @@ C469E702294CFDF700A82AB2 /* DomainsListTest.swift */, C44E985E29B23EBF0059F773 /* UpdateCheckTest.swift */, C4181F1028FAF9330042EA28 /* UITestCase.swift */, + C4CB250429B28BB800CA4492 /* MainMenuTest.swift */, ); path = ui; sourceTree = ""; @@ -2455,6 +2458,7 @@ C471E8DC28F9BB8F0021E251 /* ProgressVC.swift in Sources */, C471E8DE28F9BB8F0021E251 /* App+ConfigWatch.swift in Sources */, C471E8DF28F9BB8F0021E251 /* PhpConfigWatcher.swift in Sources */, + C4CB250529B28BB800CA4492 /* MainMenuTest.swift in Sources */, C471E8E028F9BB8F0021E251 /* Preset.swift in Sources */, C471E8E128F9BB8F0021E251 /* PresetHelper.swift in Sources */, C471E8E228F9BB8F0021E251 /* WarningView.swift in Sources */, diff --git a/phpmon/Common/PHP/PHP Version/PhpHelper.swift b/phpmon/Common/PHP/PHP Version/PhpHelper.swift index 75aa912..b248bb1 100644 --- a/phpmon/Common/PHP/PHP Version/PhpHelper.swift +++ b/phpmon/Common/PHP/PHP Version/PhpHelper.swift @@ -28,10 +28,12 @@ class PhpHelper { Task { // Create the appropriate folders and check if the files exist do { if !FileSystem.directoryExists("~/.config/phpmon/bin") { - try FileSystem.createDirectory( - "~/.config/phpmon/bin", - withIntermediateDirectories: true - ) + Task { @MainActor in + try FileSystem.createDirectory( + "~/.config/phpmon/bin", + withIntermediateDirectories: true + ) + } } if FileSystem.fileExists(destination) { @@ -59,10 +61,12 @@ class PhpHelper { export PATH=\(path):$PATH """ - try FileSystem.writeAtomicallyToFile(destination, content: script) + Task { @MainActor in + try FileSystem.writeAtomicallyToFile(destination, content: script) - if !FileSystem.isExecutableFile(destination) { - try FileSystem.makeExecutable(destination) + if !FileSystem.isExecutableFile(destination) { + try FileSystem.makeExecutable(destination) + } } // Create a symlink if the folder is not in the PATH diff --git a/phpmon/Common/Testables/TestableConfiguration.swift b/phpmon/Common/Testables/TestableConfiguration.swift index 2dbb33e..d509619 100644 --- a/phpmon/Common/Testables/TestableConfiguration.swift +++ b/phpmon/Common/Testables/TestableConfiguration.swift @@ -15,6 +15,88 @@ public struct TestableConfiguration: Codable { var commandOutput: [String: String] var preferenceOverrides: [PreferenceName: Bool] + init( + architecture: String, + filesystem: [String: FakeFile], + shellOutput: [String: BatchFakeShellOutput], + commandOutput: [String: String], + preferenceOverrides: [PreferenceName: Bool], + phpVersions: [VersionNumber] + ) { + self.architecture = architecture + self.filesystem = filesystem + self.shellOutput = shellOutput + self.commandOutput = commandOutput + self.preferenceOverrides = preferenceOverrides + + phpVersions.enumerated().forEach { (index, version) in + self.addPhpVersion(version, primary: index == 0) + } + } + + private enum CodingKeys: String, CodingKey { + case architecture, filesystem, shellOutput, commandOutput, preferenceOverrides + } + + // MARK: Add PHP versions + + private var primaryPhpVersion: VersionNumber? + private var secondaryPhpVersions: [VersionNumber] = [] + + mutating func addPhpVersion(_ version: VersionNumber, primary: Bool) { + if primary { + if primaryPhpVersion != nil { + fatalError("You cannot add multiple primary PHP versions to a testable configuration!") + } + primaryPhpVersion = version + } else { + self.secondaryPhpVersions.append(version) + } + + self.filesystem = self.filesystem.merging([ + "/opt/homebrew/opt/php@\(version.short)/bin/php" + : .fake(.symlink, "/opt/homebrew/Cellar/php/\(version.long)/bin/php"), + "/opt/homebrew/Cellar/php/\(version.long)/bin/php" + : .fake(.binary), + "/opt/homebrew/Cellar/php/\(version.long)/bin/php-config" + : .fake(.binary), + "/opt/homebrew/etc/php/\(version.short)/php-fpm.d/www.conf" + : .fake(.text), + "/opt/homebrew/etc/php/\(version.short)/php-fpm.d/valet-fpm.conf" + : .fake(.text), + "/opt/homebrew/etc/php/\(version.short)/php.ini" + : .fake(.text), + "/opt/homebrew/etc/php/\(version.short)/conf.d/php-memory-limits.ini" + : .fake(.text) + ]) { (_, new) in new } + + if primary { + self.shellOutput["ls /opt/homebrew/opt | grep php"] + = .instant("php") + self.filesystem["/opt/homebrew/opt/php"] + = .fake(.symlink, "/opt/homebrew/Cellar/php/\(version.long)") + self.filesystem["/opt/homebrew/opt/php/bin/php"] + = .fake(.symlink, "/opt/homebrew/Cellar/php/\(version.long)/bin/php") + self.filesystem["/opt/homebrew/bin/php"] + = .fake(.symlink, "/opt/homebrew/Cellar/php/\(version.long)/bin/php") + self.commandOutput["/opt/homebrew/bin/php-config --version"] + = version.long + self.commandOutput["/opt/homebrew/bin/php -r echo php_ini_scanned_files();"] = + """ + /opt/homebrew/etc/php/\(version.short)/conf.d/php-memory-limits.ini, + """ + } else { + self.shellOutput["ls /opt/homebrew/opt | grep php@"] = + BatchFakeShellOutput.instant( + self.secondaryPhpVersions + .map { "php@\($0.short)" } + .joined(separator: "\n") + ) + } + } + + // MARK: Interactions + func apply() { Log.separator() Log.info("USING TESTABLE CONFIGURATION...") @@ -38,6 +120,8 @@ public struct TestableConfiguration: Codable { } } + // MARK: Persist and load + func toJson(pretty: Bool = false) -> String { let data = try! JSONEncoder().encode(self) diff --git a/tests/Shared/TestableConfigurations.swift b/tests/Shared/TestableConfigurations.swift index e2b8ce0..4c105bb 100644 --- a/tests/Shared/TestableConfigurations.swift +++ b/tests/Shared/TestableConfigurations.swift @@ -24,16 +24,6 @@ class TestableConfigurations { : .fake(.binary), "/opt/homebrew/bin/valet" : .fake(.binary), - "/opt/homebrew/opt/php" - : .fake(.symlink, "/opt/homebrew/Cellar/php/8.2.0"), - "/opt/homebrew/opt/php@8.2/bin/php" - : .fake(.symlink, "/opt/homebrew/Cellar/php/8.2.0/bin/php"), - "/opt/homebrew/Cellar/php/8.2.0/bin/php" - : .fake(.binary), - "/opt/homebrew/Cellar/php/8.2.0/bin/php-config" - : .fake(.binary), - "/opt/homebrew/etc/php/8.2/php-fpm.d/www.conf" - : .fake(.text), "~/.config/valet/config.json" : .fake(.text, """ { @@ -45,12 +35,6 @@ class TestableConfigurations { "loopback": "127.0.0.1" } """), - "/opt/homebrew/etc/php/8.2/php-fpm.d/valet-fpm.conf" - : .fake(.text), - "/opt/homebrew/etc/php/8.2/php.ini" - : .fake(.text), - "/opt/homebrew/etc/php/8.2/conf.d/php-memory-limits.ini" - : .fake(.text) ], shellOutput: [ "sysctl -n sysctl.proc_translated" @@ -59,17 +43,6 @@ class TestableConfigurations { : .instant("user"), "which node" : .instant("/opt/homebrew/bin/node"), - "php -v" - : .instant(""" - PHP 8.2.0 (cli) (built: Dec XX 20XX XX:XX:XX) (NTS) - Copyright (c) The PHP Group - Zend Engine vX.X, Copyright (c) Zend Technologies - with Zend OPcache vX.X, Copyright (c), by Zend Technologies - """), - "ls /opt/homebrew/opt | grep php" - : .instant("php"), - "ls /opt/homebrew/opt | grep php@" - : .instant("php@8.2"), "sudo /opt/homebrew/bin/brew services info dnsmasq --json" : .delayed(0.2, """ [ @@ -176,16 +149,16 @@ class TestableConfigurations { : .instant("OK"), ], commandOutput: [ - "/opt/homebrew/bin/php-config --version": "8.2.0", "/opt/homebrew/bin/php -r echo ini_get('memory_limit');": "512M", "/opt/homebrew/bin/php -r echo ini_get('upload_max_filesize');": "512M", "/opt/homebrew/bin/php -r echo ini_get('post_max_size');": "512M", - "/opt/homebrew/bin/php -r echo php_ini_scanned_files();" - : """ - /opt/homebrew/etc/php/8.2/conf.d/php-memory-limits.ini, - """ ], - preferenceOverrides: [:] + preferenceOverrides: [:], + phpVersions: [ + VersionNumber(major: 8, minor: 2, patch: 0), + VersionNumber(major: 8, minor: 1, patch: 0), + VersionNumber(major: 8, minor: 0, patch: 0) + ] ) } } diff --git a/tests/ui/MainMenuTest.swift b/tests/ui/MainMenuTest.swift new file mode 100644 index 0000000..5c3f2b3 --- /dev/null +++ b/tests/ui/MainMenuTest.swift @@ -0,0 +1,50 @@ +// +// MainMenuTest.swift +// UI Tests +// +// Created by Nico Verbruggen on 03/03/2023. +// Copyright © 2023 Nico Verbruggen. All rights reserved. +// + +import XCTest + +final class MainMenuTest: UITestCase { + + override func setUpWithError() throws { + continueAfterFailure = false + } + + final func test_can_open_status_menu_item() throws { + let app = launch(openMenu: true) + + assertAllExist([ + // "Switch to PHP 8.2 (php)" should be visible since it is aliased to `php` + app.menuItems["\("mi_php_switch".localized) 8.2 (php)"], + // "Switch to PHP 8.1" should be the non-disabled option + app.menuItems["\("mi_php_switch".localized) 8.1 (php@8.1)"], + // "Switch to PHP 8.0" should be the non-disabled option + app.menuItems["\("mi_php_switch".localized) 8.0 (php@8.0)"], + // We should see the about and quit items + app.menuItems["mi_about".localized], + app.menuItems["mi_quit".localized] + ]) + + sleep(2) + } + + final func test_can_open_about() throws { + let app = launch(openMenu: true) + app.mainMenuItem(withText: "mi_about".localized).click() + } + + final func test_can_open_settings() throws { + let app = launch(openMenu: true) + app.mainMenuItem(withText: "mi_preferences".localized).click() + } + + final func test_can_quit_app() throws { + let app = launch(openMenu: true) + app.mainMenuItem(withText: "mi_quit".localized).click() + } + +} diff --git a/tests/ui/StartupTest.swift b/tests/ui/StartupTest.swift index 369cf26..2c00802 100644 --- a/tests/ui/StartupTest.swift +++ b/tests/ui/StartupTest.swift @@ -20,9 +20,7 @@ final class StartupTest: UITestCase { var configuration = TestableConfigurations.working configuration.filesystem["/opt/homebrew/bin/php"] = nil // PHP binary must be missing - let app = XCPMApplication() - app.withConfiguration(configuration) - app.launch() + let app = launch(with: configuration) // Dialog 1: "PHP is not correctly installed" assertAllExist([ @@ -52,9 +50,7 @@ final class StartupTest: UITestCase { var configuration = TestableConfigurations.working configuration.filesystem["/opt/homebrew/etc/php/8.2/php-fpm.d/valet-fpm.conf"] = nil - let app = XCPMApplication() - app.withConfiguration(configuration) - app.launch() + let app = launch(with: configuration) assertExists(app.staticTexts["alert.php_fpm_broken.title".localized], 3.0) click(app.buttons["generic.ok".localized]) @@ -64,30 +60,9 @@ final class StartupTest: UITestCase { var configuration = TestableConfigurations.working configuration.shellOutput["valet --version"] = .instant("Laravel Valet 5.0") - let app = XCPMApplication() - app.withConfiguration(configuration) - app.launch() + let app = launch(with: configuration) assertExists(app.staticTexts["startup.errors.valet_version_not_supported.title".localized], 3.0) click(app.buttons["generic.ok".localized]) } - - final func test_can_open_status_menu_item() throws { - let app = XCPMApplication() - app.withConfiguration(TestableConfigurations.working) - app.launch() - - // Note: If this fails here, make sure the menu bar item can be displayed - // If you use Bartender or something like this, this item may be hidden and tests will fail - app.statusItems.firstMatch.click() - - assertAllExist([ - // "Switch to PHP 8.1 (php)" should be visible since it is aliased to `php` - app.menuItems["\("mi_php_switch".localized) 8.2 (php)"], - // We should see the about and quit items - app.menuItems["mi_about".localized], - app.menuItems["mi_quit".localized] - ]) - sleep(2) - } } diff --git a/tests/ui/UITestCase.swift b/tests/ui/UITestCase.swift index 858b824..4a02651 100644 --- a/tests/ui/UITestCase.swift +++ b/tests/ui/UITestCase.swift @@ -9,7 +9,6 @@ import XCTest class UITestCase: XCTestCase { - /** Launches the app and opens the menu. */ public func launch( openMenu: Bool = false, @@ -50,7 +49,15 @@ class UITestCase: XCTestCase { public func click(_ element: XCUIElement) { element.click() } +} +extension XCPMApplication { + /** + Opens a given menu item found in the menu bar's status item. + */ + public func mainMenuItem(withText text: String) -> XCUIElement { + self.statusItems.firstMatch.menuItems[text].firstMatch + } } extension XCUIElement {