diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index dc5927e1..7dd09039 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -126,6 +126,8 @@ 03B675EA2EBA30D800EE04A9 /* NSImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B675E82EBA30D200EE04A9 /* NSImageExtension.swift */; }; 03B675EB2EBA30D800EE04A9 /* NSImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B675E82EBA30D200EE04A9 /* NSImageExtension.swift */; }; 03B675EC2EBA30D800EE04A9 /* NSImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B675E82EBA30D200EE04A9 /* NSImageExtension.swift */; }; + 03B947DE2F43692500B6F899 /* TestURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B947DD2F43691D00B6F899 /* TestURL.swift */; }; + 03B947E12F436A6B00B6F899 /* Binaries.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B947E02F436A6700B6F899 /* Binaries.swift */; }; 03BFF5272E312C3D007F96FA /* Startup+Timers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BFF5262E312C39007F96FA /* Startup+Timers.swift */; }; 03BFF5282E312C3D007F96FA /* Startup+Timers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BFF5262E312C39007F96FA /* Startup+Timers.swift */; }; 03BFF5292E312C3D007F96FA /* Startup+Timers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03BFF5262E312C39007F96FA /* Startup+Timers.swift */; }; @@ -1067,6 +1069,8 @@ 03ACC6442ECCAB140070D4CD /* RealWebApiTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealWebApiTest.swift; sourceTree = ""; }; 03ACC6462ECCBA100070D4CD /* CaskFile+API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CaskFile+API.swift"; sourceTree = ""; }; 03B675E82EBA30D200EE04A9 /* NSImageExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImageExtension.swift; sourceTree = ""; }; + 03B947DD2F43691D00B6F899 /* TestURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestURL.swift; sourceTree = ""; }; + 03B947E02F436A6700B6F899 /* Binaries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Binaries.swift; sourceTree = ""; }; 03BFF5262E312C39007F96FA /* Startup+Timers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Startup+Timers.swift"; sourceTree = ""; }; 03BFF52B2E313240007F96FA /* StatusMenu+Driver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusMenu+Driver.swift"; sourceTree = ""; }; 03C099432EA15C8B00B76D43 /* Container+Real.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Container+Real.swift"; sourceTree = ""; }; @@ -1494,6 +1498,15 @@ path = WebApi; sourceTree = ""; }; + 03B947DF2F436A4800B6F899 /* Conditions */ = { + isa = PBXGroup; + children = ( + 03B947E02F436A6700B6F899 /* Binaries.swift */, + 03B947DD2F43691D00B6F899 /* TestURL.swift */, + ); + path = Conditions; + sourceTree = ""; + }; 03BFF1D12E3CF4F2004C56A9 /* Provision */ = { isa = PBXGroup; children = ( @@ -2430,6 +2443,7 @@ C4F7807A25D7F84B000DBC97 /* unit */ = { isa = PBXGroup; children = ( + 03B947DF2F436A4800B6F899 /* Conditions */, C40C7F1C27720E1400DDDCDC /* Test Files */, C4C1019927C65A4D001FACC2 /* Commands */, 036C39062E5C8890008DAEDF /* Integration */, @@ -3518,6 +3532,7 @@ C485707728BF455300539B36 /* HeaderView.swift in Sources */, C4F780B125D80B4D000DBC97 /* PhpExtension.swift in Sources */, C4D5CFCB27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */, + 03B947E12F436A6B00B6F899 /* Binaries.swift in Sources */, 036C39142E5CB822008DAEDF /* TestBundle.swift in Sources */, C4E6840A2AF26B830023ED25 /* BrewTapFormulae.swift in Sources */, C4068CA827B07A1300544CD5 /* SelectPreferenceView.swift in Sources */, @@ -3557,6 +3572,7 @@ C4E49DEB28F7643D0026AC4E /* CommandProtocol.swift in Sources */, C4F2E4382752F08D0020E974 /* BrewDiagnostics.swift in Sources */, C485707428BF454E00539B36 /* ServicesView.swift in Sources */, + 03B947DE2F43692500B6F899 /* TestURL.swift in Sources */, C4B79EC729CA474200A483EE /* FakeCommand.swift in Sources */, C4611E5F2AEAD2FB0010BE24 /* ConfigManagerView.swift in Sources */, C4F780AE25D80B37000DBC97 /* PhpExtensionTest.swift in Sources */, diff --git a/tests/unit/Commands/CommandTest.swift b/tests/unit/Commands/CommandTest.swift index b3f26ae3..353cfeb3 100644 --- a/tests/unit/Commands/CommandTest.swift +++ b/tests/unit/Commands/CommandTest.swift @@ -7,9 +7,14 @@ // import Testing +import Foundation struct CommandTest { - @Test func determinePhpVersion() { + @Test(.enabled(if: Binaries.exist(paths: [ + "/opt/homebrew/bin/php", + "/usr/local/bin/php" + ]), "Requires PHP binary")) + func determinePhpVersion() { let container = Container.real(minimal: true) let version = container.command.execute( diff --git a/tests/unit/Conditions/Binaries.swift b/tests/unit/Conditions/Binaries.swift new file mode 100644 index 00000000..79be5f0e --- /dev/null +++ b/tests/unit/Conditions/Binaries.swift @@ -0,0 +1,19 @@ +// +// Binaries.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 16/02/2026. +// Copyright © 2026 Nico Verbruggen. All rights reserved. +// + +import Foundation + +class Binaries { + static func exist(paths: [String]) -> Bool { + for path in paths where FileManager.default.fileExists(atPath: path) { + return true + } + + return false + } +} diff --git a/tests/unit/Conditions/TestURL.swift b/tests/unit/Conditions/TestURL.swift new file mode 100644 index 00000000..5c983cd7 --- /dev/null +++ b/tests/unit/Conditions/TestURL.swift @@ -0,0 +1,33 @@ +// +// URLReachable.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 16/02/2026. +// Copyright © 2026 Nico Verbruggen. All rights reserved. +// + +import Foundation + +class TestURL { + static func isReachable(url: String) -> Bool { + let process = Process() + + process.executableURL = URL(fileURLWithPath: "/usr/bin/curl") + process.arguments = [ + "-s", "-o", "/dev/null", + "--max-time", "3", + url + ] + + process.standardOutput = Pipe() + process.standardError = Pipe() + + do { + try process.run() + process.waitUntilExit() + return process.terminationStatus == 0 + } catch { + return false + } + } +} diff --git a/tests/unit/Testables/WebApi/RealWebApiTest.swift b/tests/unit/Testables/WebApi/RealWebApiTest.swift index 1c4c848b..1fd3c1cd 100644 --- a/tests/unit/Testables/WebApi/RealWebApiTest.swift +++ b/tests/unit/Testables/WebApi/RealWebApiTest.swift @@ -20,7 +20,9 @@ struct RealWebApiTest { return container.webApi as! RealWebApi } - @Test func requestSucceeds() async { + @Test(.enabled(if: TestURL.isReachable(url: "https://api.phpmon.test/up"), + "Requires api.phpmon.test to be reachable")) + func requestSucceeds() async { let response = try! await WebApi.get( url("https://api.phpmon.test/up") ) @@ -29,7 +31,9 @@ struct RealWebApiTest { #expect(response.plainText!.contains("Response rendered in")) } - @Test func requestTimesOut() async { + @Test(.enabled(if: TestURL.isReachable(url: "https://api.phpmon.test/up"), + "Requires api.phpmon.test to be reachable")) + func requestTimesOut() async { await #expect(throws: WebApiError.timedOut) { try await WebApi.get( url("https://api.phpmon.test/up"),