XCTestBootstrap/Configuration/FBXCTestConfiguration.m (220 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "FBXCTestConfiguration.h" #import <FBControlCore/FBControlCore.h> #import "FBXCTestConfiguration.h" #import "FBCodeCoverageConfiguration.h" #import "FBXCTestProcess.h" #import "XCTestBootstrapError.h" FBXCTestType const FBXCTestTypeApplicationTest = FBXCTestTypeApplicationTestValue; FBXCTestType const FBXCTestTypeLogicTest = @"logic-test"; FBXCTestType const FBXCTestTypeListTest = @"list-test"; FBXCTestType const FBXCTestTypeUITest = @"ui-test"; @implementation FBXCTestConfiguration #pragma mark Initializers - (instancetype)initWithEnvironment:(NSDictionary<NSString *, NSString *> *)environment workingDirectory:(NSString *)workingDirectory testBundlePath:(NSString *)testBundlePath waitForDebugger:(BOOL)waitForDebugger timeout:(NSTimeInterval)timeout { self = [super init]; if (!self) { return nil; } _processUnderTestEnvironment = environment ?: @{}; _workingDirectory = workingDirectory; _testBundlePath = testBundlePath; _waitForDebugger = waitForDebugger; NSString *timeoutFromEnv = NSProcessInfo.processInfo.environment[@"FB_TEST_TIMEOUT"]; if (timeoutFromEnv) { _testTimeout = timeoutFromEnv.intValue; } else { _testTimeout = timeout > 0 ? timeout : [self defaultTimeout]; } return self; } #pragma mark Public - (NSTimeInterval)defaultTimeout { // TSAN is known to slow down all tests from running, bump the default test timeout to 1800s (instead of 500s). #if __has_feature(thread_sanitizer) || defined(__SANITIZE_THREAD__) return 1800.0; #endif return 500; } - (NSString *)testType { NSAssert(NO, @"-[%@ %@] is abstract and should be overridden", NSStringFromClass(self.class), NSStringFromSelector(_cmd)); return nil; } - (NSDictionary<NSString *, NSString *> *)buildEnvironmentWithEntries:(NSDictionary<NSString *, NSString *> *)entries { NSMutableDictionary<NSString *, NSString *> *parentEnvironment = NSProcessInfo.processInfo.environment.mutableCopy; [parentEnvironment removeObjectsForKeys:@[ @"XCTestConfigurationFilePath", ]]; NSMutableDictionary<NSString *, NSString *> *environmentOverrides = [NSMutableDictionary dictionary]; NSString *xctoolTestEnvPrefix = @"XCTOOL_TEST_ENV_"; for (NSString *key in parentEnvironment) { if ([key hasPrefix:xctoolTestEnvPrefix]) { NSString *childKey = [key substringFromIndex:xctoolTestEnvPrefix.length]; environmentOverrides[childKey] = parentEnvironment[key]; } } [environmentOverrides addEntriesFromDictionary:entries]; NSMutableDictionary<NSString *, NSString *> *environment = parentEnvironment.mutableCopy; for (NSString *key in environmentOverrides) { NSString *childKey = key; environment[childKey] = environmentOverrides[key]; } return environment.copy; } #pragma mark NSObject - (NSString *)description { NSData *data = [NSJSONSerialization dataWithJSONObject:self.jsonSerializableRepresentation options:0 error:NULL]; NSParameterAssert(data); return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; } - (BOOL)isEqual:(FBXCTestConfiguration *)object { // Class must match exactly in the class-cluster if (![object isMemberOfClass:self.class]) { return NO; } return (self.processUnderTestEnvironment == object.processUnderTestEnvironment || [self.processUnderTestEnvironment isEqualToDictionary:object.processUnderTestEnvironment]) && (self.workingDirectory == object.workingDirectory || [self.workingDirectory isEqualToString:object.workingDirectory]) && (self.testBundlePath == object.testBundlePath || [self.testBundlePath isEqualToString:object.testBundlePath]) && (self.testType == object.testType || [self.testType isEqualToString:object.testType]) && (self.waitForDebugger == object.waitForDebugger) && (self.testTimeout == object.testTimeout); } - (NSUInteger)hash { return self.processUnderTestEnvironment.hash ^ self.workingDirectory.hash ^ self.testBundlePath.hash ^ self.testType.hash ^ ((NSUInteger) self.waitForDebugger) ^ ((NSUInteger) self.testTimeout); } #pragma mark JSON static NSString *const KeyEnvironment = @"environment"; static NSString *const KeyListTestsOnly = @"list_only"; static NSString *const KeyOSLogPath = @"os_log_path"; static NSString *const KeyRunnerAppPath = @"test_host_path"; static NSString *const KeyRunnerTargetPath = @"test_target_path"; static NSString *const KeyTestArtifactsFilenameGlobs = @"test_artifacts_filename_globs"; static NSString *const KeyTestBundlePath = @"test_bundle_path"; static NSString *const KeyTestFilter = @"test_filter"; static NSString *const KeyTestMirror = @"test_mirror"; static NSString *const KeyTestTimeout = @"test_timeout"; static NSString *const KeyTestType = @"test_type"; static NSString *const KeyVideoRecordingPath = @"video_recording_path"; static NSString *const KeyWaitForDebugger = @"wait_for_debugger"; static NSString *const KeyWorkingDirectory = @"working_directory"; - (id)jsonSerializableRepresentation { return @{ KeyEnvironment: self.processUnderTestEnvironment, KeyWorkingDirectory: self.workingDirectory, KeyTestBundlePath: self.testBundlePath, KeyTestType: self.testType, KeyListTestsOnly: @NO, KeyWaitForDebugger: @(self.waitForDebugger), KeyTestTimeout: @(self.testTimeout), }; } #pragma mark NSCopying - (instancetype)copyWithZone:(NSZone *)zone { return self; } @end @implementation FBListTestConfiguration #pragma mark Initializers + (instancetype)configurationWithEnvironment:(NSDictionary<NSString *, NSString *> *)environment workingDirectory:(NSString *)workingDirectory testBundlePath:(NSString *)testBundlePath runnerAppPath:(nullable NSString *)runnerAppPath waitForDebugger:(BOOL)waitForDebugger timeout:(NSTimeInterval)timeout { return [[FBListTestConfiguration alloc] initWithEnvironment:environment workingDirectory:workingDirectory testBundlePath:testBundlePath runnerAppPath:runnerAppPath waitForDebugger:waitForDebugger timeout:timeout]; } - (instancetype)initWithEnvironment:(NSDictionary<NSString *, NSString *> *)environment workingDirectory:(NSString *)workingDirectory testBundlePath:(NSString *)testBundlePath runnerAppPath:(nullable NSString *)runnerAppPath waitForDebugger:(BOOL)waitForDebugger timeout:(NSTimeInterval)timeout { self = [super initWithEnvironment:environment workingDirectory:workingDirectory testBundlePath:testBundlePath waitForDebugger:waitForDebugger timeout:timeout]; if (!self) { return nil; } _runnerAppPath = runnerAppPath; return self; } #pragma mark Public - (NSString *)testType { return FBXCTestTypeListTest; } #pragma mark JSON - (id)jsonSerializableRepresentation { NSMutableDictionary<NSString *, id> *json = [NSMutableDictionary dictionaryWithDictionary:[super jsonSerializableRepresentation]]; json[KeyListTestsOnly] = @YES; json[KeyRunnerAppPath] = _runnerAppPath ?: NSNull.null; return [json copy]; } @end @implementation FBTestManagerTestConfiguration #pragma mark Initializers + (instancetype)configurationWithEnvironment:(NSDictionary<NSString *, NSString *> *)environment workingDirectory:(NSString *)workingDirectory testBundlePath:(NSString *)testBundlePath waitForDebugger:(BOOL)waitForDebugger timeout:(NSTimeInterval)timeout runnerAppPath:(NSString *)runnerAppPath testTargetAppPath:(NSString *)testTargetAppPath testFilter:(NSString *)testFilter videoRecordingPath:(NSString *)videoRecordingPath testArtifactsFilenameGlobs:(nullable NSArray<NSString *> *)testArtifactsFilenameGlobs osLogPath:(nullable NSString *)osLogPath { return [[FBTestManagerTestConfiguration alloc] initWithEnvironment:environment workingDirectory:workingDirectory testBundlePath:testBundlePath waitForDebugger:waitForDebugger timeout:timeout runnerAppPath:runnerAppPath testTargetAppPath:testTargetAppPath testFilter:testFilter videoRecordingPath:videoRecordingPath testArtifactsFilenameGlobs:testArtifactsFilenameGlobs osLogPath:osLogPath]; } - (instancetype)initWithEnvironment:(NSDictionary<NSString *, NSString *> *)environment workingDirectory:(NSString *)workingDirectory testBundlePath:(NSString *)testBundlePath waitForDebugger:(BOOL)waitForDebugger timeout:(NSTimeInterval)timeout runnerAppPath:(NSString *)runnerAppPath testTargetAppPath:(NSString *)testTargetAppPath testFilter:(NSString *)testFilter videoRecordingPath:(NSString *)videoRecordingPath testArtifactsFilenameGlobs:(NSArray<NSString *> *)testArtifactsFilenameGlobs osLogPath:(nullable NSString *)osLogPath { self = [super initWithEnvironment:environment workingDirectory:workingDirectory testBundlePath:testBundlePath waitForDebugger:waitForDebugger timeout:timeout]; if (!self) { return nil; } _runnerAppPath = runnerAppPath; _testTargetAppPath = testTargetAppPath; _testFilter = testFilter; _videoRecordingPath = videoRecordingPath; _testArtifactsFilenameGlobs = testArtifactsFilenameGlobs; _osLogPath = osLogPath; return self; } #pragma mark Public - (NSString *)testType { return _testTargetAppPath != nil ? FBXCTestTypeUITest : FBXCTestTypeApplicationTest; } #pragma mark JSON - (id)jsonSerializableRepresentation { NSMutableDictionary<NSString *, id> *json = [NSMutableDictionary dictionaryWithDictionary:[super jsonSerializableRepresentation]]; json[KeyRunnerAppPath] = self.runnerAppPath; json[KeyRunnerTargetPath] = self.testTargetAppPath; json[KeyTestFilter] = self.testFilter ?: NSNull.null; json[KeyVideoRecordingPath] = self.videoRecordingPath ?: NSNull.null; json[KeyTestArtifactsFilenameGlobs] = self.testArtifactsFilenameGlobs ?: NSNull.null; json[KeyOSLogPath] = self.osLogPath ?: NSNull.null; return [json copy]; } @end @implementation FBLogicTestConfiguration #pragma mark Initializers + (instancetype)configurationWithEnvironment:(NSDictionary<NSString *, NSString *> *)environment workingDirectory:(NSString *)workingDirectory testBundlePath:(NSString *)testBundlePath waitForDebugger:(BOOL)waitForDebugger timeout:(NSTimeInterval)timeout testFilter:(NSString *)testFilter mirroring:(FBLogicTestMirrorLogs)mirroring coverageConfiguration:(nullable FBCodeCoverageConfiguration *)coverageConfiguration binaryPath:(nullable NSString *)binaryPath logDirectoryPath:(NSString *)logDirectoryPath { return [[FBLogicTestConfiguration alloc] initWithEnvironment:environment workingDirectory:workingDirectory testBundlePath:testBundlePath waitForDebugger:waitForDebugger timeout:timeout testFilter:testFilter mirroring:mirroring coverageConfiguration:coverageConfiguration binaryPath:binaryPath logDirectoryPath:logDirectoryPath]; } - (instancetype)initWithEnvironment:(NSDictionary<NSString *, NSString *> *)environment workingDirectory:(NSString *)workingDirectory testBundlePath:(NSString *)testBundlePath waitForDebugger:(BOOL)waitForDebugger timeout:(NSTimeInterval)timeout testFilter:(NSString *)testFilter mirroring:(FBLogicTestMirrorLogs)mirroring coverageConfiguration:(nullable FBCodeCoverageConfiguration *)coverageConfiguration binaryPath:(nullable NSString *)binaryPath logDirectoryPath:(NSString *)logDirectoryPath { self = [super initWithEnvironment:environment workingDirectory:workingDirectory testBundlePath:testBundlePath waitForDebugger:waitForDebugger timeout:timeout]; if (!self) { return nil; } _testFilter = testFilter; _mirroring = mirroring; _coverageConfiguration = coverageConfiguration; _binaryPath = binaryPath; _logDirectoryPath = logDirectoryPath; return self; } #pragma mark Public - (NSString *)testType { return FBXCTestTypeLogicTest; } #pragma mark JSON - (id)jsonSerializableRepresentation { NSMutableDictionary<NSString *, id> *json = [NSMutableDictionary dictionaryWithDictionary:[super jsonSerializableRepresentation]]; json[KeyTestFilter] = self.testFilter ?: NSNull.null; return [json copy]; } @end