diff --git a/Samples/Common/Sources/LibraryBridge/RecordingSample.swift b/Samples/Common/Sources/LibraryBridge/RecordingSample.swift index fdea164c..d331fe2c 100644 --- a/Samples/Common/Sources/LibraryBridge/RecordingSample.swift +++ b/Samples/Common/Sources/LibraryBridge/RecordingSample.swift @@ -27,9 +27,33 @@ import Foundation import KSCrashRecording -public class RecordingSample { - public static func simpleInstall() { - let config = KSCrashConfiguration() - KSCrash.shared.install(with: config) +public struct RecordingSample { + public enum InstallationError: Error, LocalizedError { + case kscrashError(String) + case unexpectedError(String) + + public var errorDescription: String? { + switch self { + case .kscrashError(let message), .unexpectedError(let message): + return message + } + } + } + + public static func install() -> Result { + do { + let config = KSCrashConfiguration() + try KSCrash.shared.install(with: config) + print("KSCrash installed successfully") + return .success(()) + } catch let error as KSCrashInstallError { + let message = error.localizedDescription + print("Failed to install KSCrash: \(message)") + return .failure(.kscrashError(message)) + } catch { + let message = error.localizedDescription + print("Unexpected error during KSCrash installation: \(message)") + return .failure(.unexpectedError(message)) + } } } diff --git a/Samples/Common/Sources/SampleUI/SampleView.swift b/Samples/Common/Sources/SampleUI/SampleView.swift index 3232aa25..ca1cfb8b 100644 --- a/Samples/Common/Sources/SampleUI/SampleView.swift +++ b/Samples/Common/Sources/SampleUI/SampleView.swift @@ -29,8 +29,11 @@ import LibraryBridge import CrashTriggers public struct SampleView: View { + @State private var showingInstallAlert = false + @State private var installErrorMessage: String = "" + public init() { } - + public var body: some View { NavigationView { List { @@ -50,8 +53,20 @@ public struct SampleView: View { } .navigationTitle("KSCrash Sample") } - .onAppear { - RecordingSample.simpleInstall() + .onAppear(perform: installKSCrash) + .alert(isPresented: $showingInstallAlert) { + Alert( + title: Text("Installation Failed"), + message: Text(installErrorMessage), + dismissButton: .default(Text("OK")) + ) + } + } + + private func installKSCrash() { + if case let .failure(error) = RecordingSample.install() { + installErrorMessage = error.localizedDescription + showingInstallAlert = true } } } diff --git a/Sources/KSCrashInstallations/KSCrashInstallation.m b/Sources/KSCrashInstallations/KSCrashInstallation.m index ef6ce0a6..e12f4363 100644 --- a/Sources/KSCrashInstallations/KSCrashInstallation.m +++ b/Sources/KSCrashInstallations/KSCrashInstallation.m @@ -259,15 +259,12 @@ - (void)setOnCrash:(KSReportWriteCallback)onCrash } } -- (void)installWithConfiguration:(KSCrashConfiguration *)configuration +- (BOOL)installWithConfiguration:(KSCrashConfiguration *)configuration error:(NSError **)error { KSCrash *handler = [KSCrash sharedInstance]; @synchronized(handler) { g_crashHandlerData = self.crashHandlerData; - if (configuration == nil) { - configuration = [[KSCrashConfiguration alloc] init]; - } configuration.crashNotifyCallback = ^(const struct KSCrashReportWriter *_Nonnull writer) { CrashHandlerData *crashHandlerData = g_crashHandlerData; if (crashHandlerData == NULL) { @@ -284,7 +281,14 @@ - (void)installWithConfiguration:(KSCrashConfiguration *)configuration } }; - [handler installWithConfiguration:configuration]; + NSError *installError = nil; + BOOL success = [handler installWithConfiguration:configuration error:&installError]; + + if (success == NO && error != NULL) { + *error = installError; + } + + return success; } } diff --git a/Sources/KSCrashInstallations/include/KSCrashInstallation.h b/Sources/KSCrashInstallations/include/KSCrashInstallation.h index 63f248c5..7131799d 100644 --- a/Sources/KSCrashInstallations/include/KSCrashInstallation.h +++ b/Sources/KSCrashInstallations/include/KSCrashInstallation.h @@ -51,17 +51,24 @@ NS_SWIFT_NAME(CrashInstallation) @property(atomic, readwrite, assign, nullable) KSReportWriteCallback onCrash; /** Install this crash handler with a specific configuration. - * Call this method instead of `-[KSCrash installWithConfiguration:]` to set up the crash handler + * Call this method instead of `-[KSCrash installWithConfiguration:error:]` to set up the crash handler * tailored for your specific backend requirements. * * @param configuration The configuration object containing the settings for the crash handler. - * If nil, a default KSCrashConfiguration will be used. + * @param error On input, a pointer to an error object. If an error occurs, this pointer + * is set to an actual error object containing the error information. + * You may specify nil for this parameter if you do not want the error information. + * See KSCrashError.h for specific error codes that may be returned. + * + * @return YES if the installation was successful, NO otherwise. * * @note The `crashNotifyCallback` property of the provided `KSCrashConfiguration` will not take effect * when using this method. The callback will be internally managed to ensure proper integration * with the backend. + * + * @see KSCrashError.h for a complete list of possible error codes. */ -- (void)installWithConfiguration:(nullable KSCrashConfiguration *)configuration; +- (BOOL)installWithConfiguration:(KSCrashConfiguration *)configuration error:(NSError **)error; /** Convenience method to call -[KSCrash sendAllReportsWithCompletion:]. * This method will set the KSCrash sink and then send all outstanding reports. diff --git a/Sources/KSCrashRecording/KSCrash.m b/Sources/KSCrashRecording/KSCrash.m index cb5033de..8bc9245e 100644 --- a/Sources/KSCrashRecording/KSCrash.m +++ b/Sources/KSCrashRecording/KSCrash.m @@ -240,12 +240,20 @@ - (NSDictionary *)systemInfo return [dict copy]; } -- (BOOL)installWithConfiguration:(KSCrashConfiguration *)configuration +- (BOOL)installWithConfiguration:(KSCrashConfiguration *)configuration error:(NSError **)error { self.configuration = [configuration copy] ?: [KSCrashConfiguration new]; - kscrash_install(self.bundleName.UTF8String, self.basePath.UTF8String, [self.configuration toCConfiguration]); + KSCrashInstallErrorCode result = + kscrash_install(self.bundleName.UTF8String, self.basePath.UTF8String, [self.configuration toCConfiguration]); - return true; + if (result != KSCrashInstallErrorNone) { + if (error != NULL) { + *error = [self errorForInstallErrorCode:result]; + } + return NO; + } + + return YES; } - (void)sendAllReportsWithCompletion:(KSCrashReportFilterCompletion)onCompletion @@ -436,6 +444,48 @@ - (NSMutableData *)nullTerminated:(NSData *)data return mutable; } +- (NSError *)errorForInstallErrorCode:(KSCrashInstallErrorCode)errorCode +{ + NSString *errorDescription; + switch (errorCode) { + case KSCrashInstallErrorNone: + return nil; + case KSCrashInstallErrorAlreadyInstalled: + errorDescription = @"KSCrash is already installed"; + break; + case KSCrashInstallErrorInvalidParameter: + errorDescription = @"Invalid parameter provided"; + break; + case KSCrashInstallErrorPathTooLong: + errorDescription = @"Path is too long"; + break; + case KSCrashInstallErrorCouldNotCreatePath: + errorDescription = @"Could not create path"; + break; + case KSCrashInstallErrorCouldNotInitializeStore: + errorDescription = @"Could not initialize crash report store"; + break; + case KSCrashInstallErrorCouldNotInitializeMemory: + errorDescription = @"Could not initialize memory management"; + break; + case KSCrashInstallErrorCouldNotInitializeCrashState: + errorDescription = @"Could not initialize crash state"; + break; + case KSCrashInstallErrorCouldNotSetLogFilename: + errorDescription = @"Could not set log filename"; + break; + case KSCrashInstallErrorNoActiveMonitors: + errorDescription = @"No crash monitors were activated"; + break; + default: + errorDescription = @"Unknown error occurred"; + break; + } + return [NSError errorWithDomain:KSCrashErrorDomain + code:errorCode + userInfo:@{ NSLocalizedDescriptionKey : errorDescription }]; +} + // ============================================================================ #pragma mark - Notifications - // ============================================================================ diff --git a/Sources/KSCrashRecording/KSCrashC.c b/Sources/KSCrashRecording/KSCrashC.c index 8c287c75..efa1b580 100644 --- a/Sources/KSCrashRecording/KSCrashC.c +++ b/Sources/KSCrashRecording/KSCrashC.c @@ -167,15 +167,13 @@ static void setMonitors(KSCrashMonitorType monitorTypes) { g_monitoring = monitorTypes; - if (g_installed) { - for (size_t i = 0; i < g_monitorMappingCount; i++) { - KSCrashMonitorAPI *api = g_monitorMappings[i].getAPI(); - if (api != NULL) { - if (monitorTypes & g_monitorMappings[i].type) { - kscm_addMonitor(api); - } else { - kscm_removeMonitor(api); - } + for (size_t i = 0; i < g_monitorMappingCount; i++) { + KSCrashMonitorAPI *api = g_monitorMappings[i].getAPI(); + if (api != NULL) { + if (monitorTypes & g_monitorMappings[i].type) { + kscm_addMonitor(api); + } else { + kscm_removeMonitor(api); } } } @@ -211,31 +209,55 @@ void handleConfiguration(KSCrashCConfiguration *configuration) #pragma mark - API - // ============================================================================ -void kscrash_install(const char *appName, const char *const installPath, KSCrashCConfiguration configuration) +KSCrashInstallErrorCode kscrash_install(const char *appName, const char *const installPath, + KSCrashCConfiguration configuration) { KSLOG_DEBUG("Installing crash reporter."); if (g_installed) { KSLOG_DEBUG("Crash reporter already installed."); - return; + return KSCrashInstallErrorAlreadyInstalled; + } + + if (appName == NULL || installPath == NULL) { + KSLOG_ERROR("Invalid parameters: appName or installPath is NULL."); + return KSCrashInstallErrorInvalidParameter; } - g_installed = 1; handleConfiguration(&configuration); char path[KSFU_MAX_PATH_LENGTH]; - snprintf(path, sizeof(path), "%s/Reports", installPath); - ksfu_makePath(path); + if (snprintf(path, sizeof(path), "%s/Reports", installPath) >= (int)sizeof(path)) { + KSLOG_ERROR("Path too long."); + return KSCrashInstallErrorPathTooLong; + } + if (ksfu_makePath(path) == false) { + KSLOG_ERROR("Could not create path: %s", path); + return KSCrashInstallErrorCouldNotCreatePath; + } kscrs_initialize(appName, installPath, path); - snprintf(path, sizeof(path), "%s/Data", installPath); - ksfu_makePath(path); + if (snprintf(path, sizeof(path), "%s/Data", installPath) >= (int)sizeof(path)) { + KSLOG_ERROR("Path too long."); + return KSCrashInstallErrorPathTooLong; + } + if (ksfu_makePath(path) == false) { + KSLOG_ERROR("Could not create path: %s", path); + return KSCrashInstallErrorCouldNotCreatePath; + } ksmemory_initialize(path); - snprintf(path, sizeof(path), "%s/Data/CrashState.json", installPath); + if (snprintf(path, sizeof(path), "%s/Data/CrashState.json", installPath) >= (int)sizeof(path)) { + KSLOG_ERROR("Path too long."); + return KSCrashInstallErrorPathTooLong; + } kscrashstate_initialize(path); - snprintf(g_consoleLogPath, sizeof(g_consoleLogPath), "%s/Data/ConsoleLog.txt", installPath); + if (snprintf(g_consoleLogPath, sizeof(g_consoleLogPath), "%s/Data/ConsoleLog.txt", installPath) >= + (int)sizeof(g_consoleLogPath)) { + KSLOG_ERROR("Console log path too long."); + return KSCrashInstallErrorPathTooLong; + } if (g_shouldPrintPreviousLog) { printPreviousLog(g_consoleLogPath); } @@ -245,11 +267,16 @@ void kscrash_install(const char *appName, const char *const installPath, KSCrash kscm_setEventCallback(onCrash); setMonitors(configuration.monitors); - kscm_activateMonitors(); + if (kscm_activateMonitors() == false) { + KSLOG_ERROR("No crash monitors are active"); + return KSCrashInstallErrorNoActiveMonitors; + } + g_installed = true; KSLOG_DEBUG("Installation complete."); notifyOfBeforeInstallationState(); + return KSCrashInstallErrorNone; } void kscrash_setUserInfoJSON(const char *const userInfoJSON) { kscrashreport_setUserInfoJSON(userInfoJSON); } diff --git a/Sources/KSCrashRecording/include/KSCrash.h b/Sources/KSCrashRecording/include/KSCrash.h index 71a92940..b26701bb 100644 --- a/Sources/KSCrashRecording/include/KSCrash.h +++ b/Sources/KSCrashRecording/include/KSCrash.h @@ -128,12 +128,18 @@ NS_ASSUME_NONNULL_BEGIN + (void)setBasePath:(nullable NSString *)basePath; /** Install the crash reporter. - * The reporter will record crashes, but will not send any crash reports unless - * sink is set. + * The reporter will record crashes, but will not send any crash reports unless a sink is set. * - * @return YES if the reporter successfully installed. + * @param configuration The configuration to use for installation. + * @param error A pointer to an NSError object. If an error occurs, this pointer is set to an actual error object + * containing the error information. You may specify nil for this parameter if you do not want + * the error information. + * @return YES if the reporter successfully installed, NO otherwise. + * + * @note If the installation fails, the error parameter will contain information about the failure reason. + * @note Once installed, the crash reporter cannot be re-installed or modified without restarting the application. */ -- (BOOL)installWithConfiguration:(KSCrashConfiguration *)configuration; +- (BOOL)installWithConfiguration:(KSCrashConfiguration *)configuration error:(NSError **)error; /** Send all outstanding crash reports to the current sink. * It will only attempt to send the most recent 5 reports. All others will be diff --git a/Sources/KSCrashRecording/include/KSCrashC.h b/Sources/KSCrashRecording/include/KSCrashC.h index a9a58014..5c109015 100644 --- a/Sources/KSCrashRecording/include/KSCrashC.h +++ b/Sources/KSCrashRecording/include/KSCrashC.h @@ -33,6 +33,7 @@ #include #include "KSCrashCConfiguration.h" +#include "KSCrashError.h" #include "KSCrashMonitorType.h" #include "KSCrashReportWriter.h" @@ -54,19 +55,25 @@ extern "C" { * The specified directory must be writable, as it will contain log files, * crash data, and other diagnostic information. * - * @param configuration A `KSCrashConfiguration` struct containing various settings and options + * @param configuration A `KSCrashCConfiguration` struct containing various settings and options * for the crash reporter. This struct allows you to specify which types of crashes * to monitor, user-supplied metadata, memory introspection options, * and other advanced settings. * Each field in the configuration struct has default values, which can be overridden * to tailor the behavior of the crash reporter to your specific requirements. * + * @return KSCrashInstallErrorCode indicating the result of the installation. + * 0 if installation was successful, other values indicate specific errors. + * * Example usage: * ``` - * KSCrashConfiguration config = KSCrashConfiguration_Default; + * KSCrashCConfiguration config = KSCrashCConfiguration_Default; * config.monitors = KSCrashMonitorTypeAll; * config.userInfoJSON = "{ \"user\": \"example\" }"; - * kscrash_install("MyApp", "/path/to/install", config); + * KSCrashInstallErrorCode result = kscrash_install("MyApp", "/path/to/install", config); + * if (result != 0) { + * // Handle installation error + * } * ``` * * @note This function must be called before any crashes occur to ensure that @@ -75,7 +82,8 @@ extern "C" { * @note Once installed, the crash reporter cannot be re-installed or modified * without restarting the application. */ -void kscrash_install(const char *appName, const char *const installPath, KSCrashCConfiguration configuration); +KSCrashInstallErrorCode kscrash_install(const char *appName, const char *const installPath, + KSCrashCConfiguration configuration); /** Set the user-supplied data in JSON format. * diff --git a/Sources/KSCrashRecording/include/KSCrashError.h b/Sources/KSCrashRecording/include/KSCrashError.h new file mode 100644 index 00000000..63429273 --- /dev/null +++ b/Sources/KSCrashRecording/include/KSCrashError.h @@ -0,0 +1,67 @@ +// +// KSCrashError.h +// +// Created by Gleb Linnik on 12.07.2024. +// +// Copyright (c) 2012 Karl Stenerud. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall remain in place +// in this source code. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#ifndef KSCrashError_h +#define KSCrashError_h + +#ifdef __OBJC__ +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __OBJC__ +static NSErrorDomain const KSCrashErrorDomain = @"KSCrashErrorDomain"; +#endif + +typedef +#ifdef __OBJC__ + NS_ERROR_ENUM(KSCrashErrorDomain, KSCrashInstallErrorCode) +#else /* __OBJC__ */ + enum +#endif /* __OBJC__ */ +{ KSCrashInstallErrorNone = 0, + KSCrashInstallErrorAlreadyInstalled, + KSCrashInstallErrorInvalidParameter, + KSCrashInstallErrorPathTooLong, + KSCrashInstallErrorCouldNotCreatePath, + KSCrashInstallErrorCouldNotInitializeStore, + KSCrashInstallErrorCouldNotInitializeMemory, + KSCrashInstallErrorCouldNotInitializeCrashState, + KSCrashInstallErrorCouldNotSetLogFilename, + KSCrashInstallErrorNoActiveMonitors } +#ifndef __OBJC__ +KSCrashInstallErrorCode +#endif + ; + +#ifdef __cplusplus +} +#endif + +#endif /* KSCrashError_h */ diff --git a/Sources/KSCrashRecordingCore/KSCrashMonitor.c b/Sources/KSCrashRecordingCore/KSCrashMonitor.c index d9510dfd..60033c50 100644 --- a/Sources/KSCrashRecordingCore/KSCrashMonitor.c +++ b/Sources/KSCrashRecordingCore/KSCrashMonitor.c @@ -144,7 +144,7 @@ void kscm_setEventCallback(void (*onEvent)(struct KSCrash_MonitorContext *monito g_onExceptionEvent = onEvent; } -void kscm_activateMonitors(void) +bool kscm_activateMonitors(void) { // Check for debugger and async safety bool isDebuggerUnsafe = ksdebug_isBeingTraced(); @@ -182,12 +182,15 @@ void kscm_activateMonitors(void) kscm_setMonitorEnabled(api, shouldEnable); } + bool anyMonitorActive = false; + // Log active monitors KSLOG_DEBUG("Active monitors are now:"); for (size_t i = 0; i < g_monitors.count; i++) { KSCrashMonitorAPI *api = g_monitors.apis[i]; if (kscm_isMonitorEnabled(api)) { KSLOG_DEBUG("Monitor %s is enabled.", getMonitorNameForLogging(api)); + anyMonitorActive = true; } else { KSLOG_DEBUG("Monitor %s is disabled.", getMonitorNameForLogging(api)); } @@ -198,6 +201,8 @@ void kscm_activateMonitors(void) KSCrashMonitorAPI *api = g_monitors.apis[i]; kscm_notifyPostSystemEnable(api); } + + return anyMonitorActive; } void kscm_disableAllMonitors(void) diff --git a/Sources/KSCrashRecordingCore/include/KSCrashMonitor.h b/Sources/KSCrashRecordingCore/include/KSCrashMonitor.h index e1d552d4..d74bc146 100644 --- a/Sources/KSCrashRecordingCore/include/KSCrashMonitor.h +++ b/Sources/KSCrashRecordingCore/include/KSCrashMonitor.h @@ -63,8 +63,10 @@ typedef struct { * measures for asynchronous operations may not be activated. The function * checks the current environment and adjusts the activation status of each * monitor accordingly. + * + * @return bool True if at least one monitor was successfully activated, false if no monitors were activated. */ -void kscm_activateMonitors(void); +bool kscm_activateMonitors(void); /** * Disables all active crash monitors. diff --git a/Tests/KSCrashInstallationsTests/KSCrashInstallationEmail_Tests.m b/Tests/KSCrashInstallationsTests/KSCrashInstallationEmail_Tests.m index 1dec1403..39f93e65 100644 --- a/Tests/KSCrashInstallationsTests/KSCrashInstallationEmail_Tests.m +++ b/Tests/KSCrashInstallationsTests/KSCrashInstallationEmail_Tests.m @@ -41,7 +41,7 @@ - (void)testInstall installation.message = @"message"; installation.filenameFmt = @"someFile.txt.gz"; [installation addConditionalAlertWithTitle:@"title" message:@"message" yesAnswer:@"Yes" noAnswer:@"No"]; - [installation installWithConfiguration:nil]; + [installation installWithConfiguration:[KSCrashConfiguration new] error:NULL]; [installation sendAllReportsWithCompletion:^(__unused NSArray *filteredReports, BOOL completed, NSError *error) { // There are no reports, so this will succeed. XCTAssertTrue(completed, @""); @@ -60,7 +60,7 @@ - (void)testInstallInvalid installation.filenameFmt = nil; #pragma clang diagnostic pop [installation addUnconditionalAlertWithTitle:@"title" message:@"message" dismissButtonText:@"dismiss"]; - [installation installWithConfiguration:nil]; + [installation installWithConfiguration:[KSCrashConfiguration new] error:NULL]; [installation sendAllReportsWithCompletion:^(__unused NSArray *filteredReports, BOOL completed, NSError *error) { XCTAssertFalse(completed, @""); XCTAssertNotNil(error, @""); diff --git a/Tests/KSCrashInstallationsTests/KSCrashInstallationStandard_Tests.m b/Tests/KSCrashInstallationsTests/KSCrashInstallationStandard_Tests.m index 5ffb8fe3..e4bd6422 100644 --- a/Tests/KSCrashInstallationsTests/KSCrashInstallationStandard_Tests.m +++ b/Tests/KSCrashInstallationsTests/KSCrashInstallationStandard_Tests.m @@ -37,7 +37,7 @@ - (void)testInstall { KSCrashInstallationStandard *installation = [KSCrashInstallationStandard sharedInstance]; installation.url = [NSURL URLWithString:@"www.google.com"]; - [installation installWithConfiguration:nil]; + [installation installWithConfiguration:[KSCrashConfiguration new] error:NULL]; [installation sendAllReportsWithCompletion:^(__unused NSArray *filteredReports, BOOL completed, NSError *error) { // There are no reports, so this will succeed. XCTAssertTrue(completed, @""); diff --git a/Tests/KSCrashRecordingCoreTests/KSCrashMonitor_Tests.m b/Tests/KSCrashRecordingCoreTests/KSCrashMonitor_Tests.m index f320bdaa..5b1564c0 100644 --- a/Tests/KSCrashRecordingCoreTests/KSCrashMonitor_Tests.m +++ b/Tests/KSCrashRecordingCoreTests/KSCrashMonitor_Tests.m @@ -25,6 +25,7 @@ // #import +#import #import "KSCrashMonitor.h" @@ -116,6 +117,34 @@ - (void)testDisablingAllMonitors XCTAssertFalse(g_dummyMonitor.isEnabled(), @"The monitor should be disabled after calling disable all."); } +- (void)testActivateMonitorsReturnsTrue +{ + XCTAssertTrue(kscm_addMonitor(&g_dummyMonitor), @"Monitor should be successfully added."); + XCTAssertTrue(kscm_activateMonitors(), + @"activateMonitors should return true when at least one monitor is activated."); + XCTAssertTrue(g_dummyMonitor.isEnabled(), @"The monitor should be enabled after activation."); +} + +- (void)testActivateMonitorsReturnsFalseWhenNoMonitorsActive +{ + // Don't add any monitors + XCTAssertFalse(kscm_activateMonitors(), @"activateMonitors should return false when no monitors are active."); +} + +- (void)testActivateMonitorsReturnsFalseWhenAllMonitorsDisabled +{ + KSCrashMonitorAPI alwaysDisabledMonitor = g_dummyMonitor; + alwaysDisabledMonitor.setEnabled = (void (*)(bool))imp_implementationWithBlock(^(bool isEnabled) { + // pass + }); + alwaysDisabledMonitor.isEnabled = (bool (*)(void))imp_implementationWithBlock(^{ + return false; + }); + + XCTAssertTrue(kscm_addMonitor(&alwaysDisabledMonitor), @"Monitor should be successfully added."); + XCTAssertFalse(kscm_activateMonitors(), @"activateMonitors should return false when all monitors are disabled."); +} + #pragma mark - Monitor API Null Checks - (void)testAddMonitorWithNullAPI