From 1910a93906e7432830a3020b0ae030b64a36032b Mon Sep 17 00:00:00 2001 From: Lucas Statler Date: Thu, 30 Mar 2023 12:16:25 -0700 Subject: [PATCH] Add logic test support Signed-off-by: lthrockmorton --- .gitignore | 1 + BPSampleApp/BPLogicTests-Info.plist | 22 + .../BPSampleApp.xcodeproj/project.pbxproj | 213 +++++++++ .../xcschemes/BPLogicTests.xcscheme | 52 ++ .../xcschemes/BPPassingLogicTests.xcscheme | 78 +++ .../xcschemes/BPSampleApp.xcscheme | 48 ++ BPSampleApp/BPSampleAppTests-Info.plist | 22 + .../LogicTests/BPLogicTests/BPLogicTests.m | 81 ++++ .../BPPassingLogicTests/BPBulkLogicTests.m | 219 +++++++++ .../BPPassingLogicTests/BPPassingLogicTests.m | 34 ++ .../BPPassingLogicTests/SwiftLogicTests.swift | 26 + BPSampleApp/LogicTests/Info.plist | 22 + BPTestInspector/BPTestCaseInfo.h | 42 ++ BPTestInspector/BPTestCaseInfo.m | 81 ++++ .../BPTestInspector.xcodeproj/project.pbxproj | 443 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcschemes/BPMacTestInspector.xcscheme | 67 +++ .../xcschemes/BPTestInspector.xcscheme | 67 +++ BPTestInspector/BPTestInspectorConstants.h | 22 + BPTestInspector/BPTestInspectorConstants.m | 26 + .../Configs/BPMacTestInspector.xcconfig | 8 + .../Configs/BPTestInspector.xcconfig | 9 + BPTestInspector/Internal/BPLoggingUtils.h | 28 ++ BPTestInspector/Internal/BPLoggingUtils.m | 22 + .../Internal/BPTestCaseInfo+Internal.h | 27 ++ BPTestInspector/Internal/BPXCTestUtils.h | 28 ++ BPTestInspector/Internal/BPXCTestUtils.m | 98 ++++ .../Internal/PrivateHeaders/CDStructures.h | 57 +++ .../Protocols/NSObject-Protocol.h | 33 ++ .../Protocols/XCTActivity-Protocol.h | 15 + .../Protocols/XCTIssueHandling-Protocol.h | 16 + .../Protocols/XCTWaiterDelegate-Protocol.h | 17 + .../Protocols/XCTestObservation-Protocol.h | 27 ++ .../_XCTestObservationInternal-Protocol.h | 17 + .../_XCTestObservationPrivate-Protocol.h | 17 + .../Internal/PrivateHeaders/XCTest.h | 55 +++ .../Internal/PrivateHeaders/XCTestCase.h | 189 ++++++++ .../Internal/PrivateHeaders/XCTestLog.h | 59 +++ .../Internal/PrivateHeaders/XCTestObserver.h | 23 + .../Internal/PrivateHeaders/XCTestRun.h | 73 +++ .../Internal/PrivateHeaders/XCTestSuite.h | 70 +++ .../Internal/PrivateHeaders/XCTestSuiteRun.h | 33 ++ BPTestInspector/Internal/main.m | 55 +++ Bluepill.xcworkspace/contents.xcworkspacedata | 3 + .../xcshareddata/WorkspaceSettings.xcsettings | 2 + Configs/BPMacTestInspector.xcconfig | 8 + Configs/BPTestInspector.xcconfig | 9 + bluepill/bluepill.xcodeproj/project.pbxproj | 50 ++ .../xcshareddata/xcschemes/bluepill.xcscheme | 16 +- bluepill/libBPMacTestInspector.dylib | Bin 0 -> 173584 bytes bluepill/src/BPApp.h | 2 +- bluepill/src/BPApp.m | 37 +- bluepill/src/BPHTMLReportWriter.m | 2 +- bluepill/src/BPPacker.h | 3 +- bluepill/src/BPPacker.m | 10 +- bluepill/src/BPReportCollector.m | 2 +- bluepill/src/BPRunner.h | 3 +- bluepill/src/BPRunner.m | 13 +- bluepill/src/BPSwimlane.h | 3 +- bluepill/src/BPSwimlane.m | 2 +- bluepill/src/main.m | 5 +- bluepill/tests/BPAppTests.m | 6 +- bluepill/tests/BPCLITests.m | 4 +- bluepill/tests/BPIntegrationTests.m | 159 ++++++- bluepill/tests/BPPackerTests.m | 6 +- bluepill/tests/BPRunnerTests.m | 5 +- .../BPLogicTestFixture_swift_x86_64 | Bin 0 -> 376352 bytes .../Info.plist | Bin 0 -> 750 bytes .../_CodeSignature/CodeResources | 101 ++++ .../BPLogicTestFixture_x86_64 | Bin 0 -> 71616 bytes .../Info.plist | Bin 0 -> 744 bytes .../_CodeSignature/CodeResources | 101 ++++ bp/bp.xcodeproj/project.pbxproj | 122 ++++- .../xcshareddata/xcschemes/bp.xcscheme | 28 ++ bp/libBPMacTestInspector.dylib | Bin 0 -> 173584 bytes bp/libBPTestInspector.dylib | Bin 0 -> 179504 bytes bp/src/BPConfiguration.h | 5 + bp/src/BPConfiguration.m | 9 +- bp/src/BPHandler.h | 2 + bp/src/BPHandler.m | 6 +- bp/src/BPSimulator.h | 20 + bp/src/BPSimulator.m | 263 ++++++++++- bp/src/BPStats.h | 3 + bp/src/BPTestInspectionHandler.h | 27 ++ bp/src/BPTestInspectionHandler.m | 44 ++ bp/src/BPUtils.h | 52 +- bp/src/BPUtils.m | 258 +++++++++- bp/src/Bluepill.m | 225 ++++++++- .../PrivateHeaders/CoreSimulator/SimDevice.h | 32 +- bp/src/SimulatorHelper.h | 32 ++ bp/src/SimulatorHelper.m | 82 ++++ bp/src/SimulatorMonitor.m | 20 +- bp/tests/BPCLITests.m | 2 + bp/tests/BPIntTestCase.m | 40 +- bp/tests/BPReportTests.m | 6 +- bp/tests/BPTestHelper.h | 16 + bp/tests/BPTestHelper.m | 23 + ...{BluepillTests.m => BluepillHostedTests.m} | 12 +- bp/tests/Unhosted Tests/BPTestCaseInfoTests.m | 42 ++ .../BluepillUnhostedBatchingTests.m | 125 +++++ .../Unhosted Tests/BluepillUnhostedTests.m | 176 +++++++ bp/tests/Utils/BPTestUtils.h | 29 ++ bp/tests/Utils/BPTestUtils.m | 117 +++++ bptestrunner/BUILD.bazel | 12 + bptestrunner/bluepill_batch_test.bzl | 45 +- .../bluepill_batch_test_runner.template.sh | 44 +- scripts/bluepill.sh | 26 +- 108 files changed, 4852 insertions(+), 202 deletions(-) create mode 100644 BPSampleApp/BPLogicTests-Info.plist create mode 100644 BPSampleApp/BPSampleApp.xcodeproj/xcshareddata/xcschemes/BPLogicTests.xcscheme create mode 100644 BPSampleApp/BPSampleApp.xcodeproj/xcshareddata/xcschemes/BPPassingLogicTests.xcscheme create mode 100644 BPSampleApp/BPSampleAppTests-Info.plist create mode 100644 BPSampleApp/LogicTests/BPLogicTests/BPLogicTests.m create mode 100644 BPSampleApp/LogicTests/BPPassingLogicTests/BPBulkLogicTests.m create mode 100644 BPSampleApp/LogicTests/BPPassingLogicTests/BPPassingLogicTests.m create mode 100644 BPSampleApp/LogicTests/BPPassingLogicTests/SwiftLogicTests.swift create mode 100644 BPSampleApp/LogicTests/Info.plist create mode 100644 BPTestInspector/BPTestCaseInfo.h create mode 100644 BPTestInspector/BPTestCaseInfo.m create mode 100644 BPTestInspector/BPTestInspector.xcodeproj/project.pbxproj create mode 100644 BPTestInspector/BPTestInspector.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 BPTestInspector/BPTestInspector.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 BPTestInspector/BPTestInspector.xcodeproj/xcshareddata/xcschemes/BPMacTestInspector.xcscheme create mode 100644 BPTestInspector/BPTestInspector.xcodeproj/xcshareddata/xcschemes/BPTestInspector.xcscheme create mode 100644 BPTestInspector/BPTestInspectorConstants.h create mode 100644 BPTestInspector/BPTestInspectorConstants.m create mode 100644 BPTestInspector/Configs/BPMacTestInspector.xcconfig create mode 100644 BPTestInspector/Configs/BPTestInspector.xcconfig create mode 100644 BPTestInspector/Internal/BPLoggingUtils.h create mode 100644 BPTestInspector/Internal/BPLoggingUtils.m create mode 100644 BPTestInspector/Internal/BPTestCaseInfo+Internal.h create mode 100644 BPTestInspector/Internal/BPXCTestUtils.h create mode 100644 BPTestInspector/Internal/BPXCTestUtils.m create mode 100644 BPTestInspector/Internal/PrivateHeaders/CDStructures.h create mode 100644 BPTestInspector/Internal/PrivateHeaders/Protocols/NSObject-Protocol.h create mode 100644 BPTestInspector/Internal/PrivateHeaders/Protocols/XCTActivity-Protocol.h create mode 100644 BPTestInspector/Internal/PrivateHeaders/Protocols/XCTIssueHandling-Protocol.h create mode 100644 BPTestInspector/Internal/PrivateHeaders/Protocols/XCTWaiterDelegate-Protocol.h create mode 100644 BPTestInspector/Internal/PrivateHeaders/Protocols/XCTestObservation-Protocol.h create mode 100644 BPTestInspector/Internal/PrivateHeaders/Protocols/_XCTestObservationInternal-Protocol.h create mode 100644 BPTestInspector/Internal/PrivateHeaders/Protocols/_XCTestObservationPrivate-Protocol.h create mode 100644 BPTestInspector/Internal/PrivateHeaders/XCTest.h create mode 100644 BPTestInspector/Internal/PrivateHeaders/XCTestCase.h create mode 100644 BPTestInspector/Internal/PrivateHeaders/XCTestLog.h create mode 100644 BPTestInspector/Internal/PrivateHeaders/XCTestObserver.h create mode 100644 BPTestInspector/Internal/PrivateHeaders/XCTestRun.h create mode 100644 BPTestInspector/Internal/PrivateHeaders/XCTestSuite.h create mode 100644 BPTestInspector/Internal/PrivateHeaders/XCTestSuiteRun.h create mode 100644 BPTestInspector/Internal/main.m create mode 100644 Configs/BPMacTestInspector.xcconfig create mode 100644 Configs/BPTestInspector.xcconfig create mode 100755 bluepill/libBPMacTestInspector.dylib create mode 100755 bluepill/tests/Resource Files/BPLogicTestFixture_swift_x86_64.xctest/BPLogicTestFixture_swift_x86_64 create mode 100644 bluepill/tests/Resource Files/BPLogicTestFixture_swift_x86_64.xctest/Info.plist create mode 100644 bluepill/tests/Resource Files/BPLogicTestFixture_swift_x86_64.xctest/_CodeSignature/CodeResources create mode 100755 bluepill/tests/Resource Files/BPLogicTestFixture_x86_64.xctest/BPLogicTestFixture_x86_64 create mode 100644 bluepill/tests/Resource Files/BPLogicTestFixture_x86_64.xctest/Info.plist create mode 100644 bluepill/tests/Resource Files/BPLogicTestFixture_x86_64.xctest/_CodeSignature/CodeResources create mode 100755 bp/libBPMacTestInspector.dylib create mode 100755 bp/libBPTestInspector.dylib create mode 100644 bp/src/BPTestInspectionHandler.h create mode 100644 bp/src/BPTestInspectionHandler.m rename bp/tests/{BluepillTests.m => BluepillHostedTests.m} (98%) create mode 100644 bp/tests/Unhosted Tests/BPTestCaseInfoTests.m create mode 100644 bp/tests/Unhosted Tests/BluepillUnhostedBatchingTests.m create mode 100644 bp/tests/Unhosted Tests/BluepillUnhostedTests.m create mode 100644 bp/tests/Utils/BPTestUtils.h create mode 100644 bp/tests/Utils/BPTestUtils.m diff --git a/.gitignore b/.gitignore index 9e5f1a97..3f01686f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ bp/BPVersion.h bluepill/src/BPTestReportHTML.h result.txt +result_bptestinspector.txt # Xcode # diff --git a/BPSampleApp/BPLogicTests-Info.plist b/BPSampleApp/BPLogicTests-Info.plist new file mode 100644 index 00000000..6c6c23c4 --- /dev/null +++ b/BPSampleApp/BPLogicTests-Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/BPSampleApp/BPSampleApp.xcodeproj/project.pbxproj b/BPSampleApp/BPSampleApp.xcodeproj/project.pbxproj index be65cc0a..ca5187e8 100644 --- a/BPSampleApp/BPSampleApp.xcodeproj/project.pbxproj +++ b/BPSampleApp/BPSampleApp.xcodeproj/project.pbxproj @@ -24,6 +24,10 @@ BAFCCA601E36DC2000E33C31 /* BPSampleAppUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = BAFCCA5F1E36DC2000E33C31 /* BPSampleAppUITests.m */; }; E492360122EF61F300395D98 /* BPSampleAppMoarTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E492360022EF61F300395D98 /* BPSampleAppMoarTests.m */; }; E4F8A34326F3B1AD00FE1267 /* BPSampleAppNewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E4F8A33B26F3B12F00FE1267 /* BPSampleAppNewTests.m */; }; + FBBBD90029A06B7B002B9115 /* BPLogicTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FBBBD8F129A06B47002B9115 /* BPLogicTests.m */; }; + FBD772B52A33E15D0098CEFD /* BPPassingLogicTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FBD772B12A33E0620098CEFD /* BPPassingLogicTests.m */; }; + FBD772C52A3483310098CEFD /* SwiftLogicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBD772C32A34832D0098CEFD /* SwiftLogicTests.swift */; }; + FBD772C72A378F440098CEFD /* BPBulkLogicTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FBD772C62A378F440098CEFD /* BPBulkLogicTests.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -115,6 +119,13 @@ E492360022EF61F300395D98 /* BPSampleAppMoarTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BPSampleAppMoarTests.m; sourceTree = ""; }; E4F8A33B26F3B12F00FE1267 /* BPSampleAppNewTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BPSampleAppNewTests.m; sourceTree = ""; }; E4F8A34A26F3B1AD00FE1267 /* BPSampleAppNewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BPSampleAppNewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + FBBBD8F129A06B47002B9115 /* BPLogicTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BPLogicTests.m; sourceTree = ""; }; + FBBBD8FE29A06B54002B9115 /* BPLogicTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BPLogicTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + FBD772B12A33E0620098CEFD /* BPPassingLogicTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BPPassingLogicTests.m; sourceTree = ""; }; + FBD772BC2A33E15D0098CEFD /* BPPassingLogicTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BPPassingLogicTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + FBD772BD2A33E15D0098CEFD /* BPLogicTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "BPLogicTests-Info.plist"; path = "/Users/lthrockm/ios/bluepill/bluepill/BPSampleApp/BPLogicTests-Info.plist"; sourceTree = ""; }; + FBD772C32A34832D0098CEFD /* SwiftLogicTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftLogicTests.swift; sourceTree = ""; }; + FBD772C62A378F440098CEFD /* BPBulkLogicTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BPBulkLogicTests.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -174,6 +185,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + FBBBD8F929A06B54002B9115 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FBD772B72A33E15D0098CEFD /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -210,6 +235,7 @@ isa = PBXGroup; children = ( BAB24F301DB5D45E00867756 /* BPSampleApp */, + FBBBD8F029A06B47002B9115 /* LogicTests */, BAB24F4A1DB5D45E00867756 /* BPSampleAppTests */, BAB24F5B1DB5D83C00867756 /* BPAppNegativeTests */, BAA4DA381DC3C02B00A58BCC /* BPSampleAppCrashingTests */, @@ -217,6 +243,7 @@ BA9C2DD31DD7F182007CB967 /* BPSampleAppFatalErrorTests */, BAFCCA5E1E36DC2000E33C31 /* BPSampleAppUITests */, BAB24F2F1DB5D45E00867756 /* Products */, + FBD772BD2A33E15D0098CEFD /* BPLogicTests-Info.plist */, ); sourceTree = ""; }; @@ -231,6 +258,8 @@ BA9C2DD21DD7F182007CB967 /* BPSampleAppFatalErrorTests.xctest */, BAFCCA5D1E36DC2000E33C31 /* BPSampleAppUITests.xctest */, E4F8A34A26F3B1AD00FE1267 /* BPSampleAppNewTests.xctest */, + FBBBD8FE29A06B54002B9115 /* BPLogicTests.xctest */, + FBD772BC2A33E15D0098CEFD /* BPPassingLogicTests.xctest */, ); name = Products; sourceTree = ""; @@ -291,6 +320,33 @@ path = BPSampleAppUITests; sourceTree = ""; }; + FBBBD8F029A06B47002B9115 /* LogicTests */ = { + isa = PBXGroup; + children = ( + FBD772C82A378F6F0098CEFD /* BPLogicTests */, + FBD772C92A378F7E0098CEFD /* BPPassingLogicTests */, + ); + path = LogicTests; + sourceTree = ""; + }; + FBD772C82A378F6F0098CEFD /* BPLogicTests */ = { + isa = PBXGroup; + children = ( + FBBBD8F129A06B47002B9115 /* BPLogicTests.m */, + ); + path = BPLogicTests; + sourceTree = ""; + }; + FBD772C92A378F7E0098CEFD /* BPPassingLogicTests */ = { + isa = PBXGroup; + children = ( + FBD772B12A33E0620098CEFD /* BPPassingLogicTests.m */, + FBD772C62A378F440098CEFD /* BPBulkLogicTests.m */, + FBD772C32A34832D0098CEFD /* SwiftLogicTests.swift */, + ); + path = BPPassingLogicTests; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -437,6 +493,40 @@ productReference = E4F8A34A26F3B1AD00FE1267 /* BPSampleAppNewTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + FBBBD8F229A06B54002B9115 /* BPLogicTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = FBBBD8FB29A06B54002B9115 /* Build configuration list for PBXNativeTarget "BPLogicTests" */; + buildPhases = ( + FBBBD8F529A06B54002B9115 /* Sources */, + FBBBD8F929A06B54002B9115 /* Frameworks */, + FBBBD8FA29A06B54002B9115 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = BPLogicTests; + productName = BPSampleAppTests; + productReference = FBBBD8FE29A06B54002B9115 /* BPLogicTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + FBD772B32A33E15D0098CEFD /* BPPassingLogicTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = FBD772B92A33E15D0098CEFD /* Build configuration list for PBXNativeTarget "BPPassingLogicTests" */; + buildPhases = ( + FBD772B42A33E15D0098CEFD /* Sources */, + FBD772B72A33E15D0098CEFD /* Frameworks */, + FBD772B82A33E15D0098CEFD /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = BPPassingLogicTests; + productName = BPSampleAppTests; + productReference = FBD772BC2A33E15D0098CEFD /* BPPassingLogicTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -491,6 +581,12 @@ E4F8A33D26F3B1AD00FE1267 = { DevelopmentTeam = 57Y47U492U; }; + FBBBD8F229A06B54002B9115 = { + DevelopmentTeam = 57Y47U492U; + }; + FBD772B32A33E15D0098CEFD = { + DevelopmentTeam = 57Y47U492U; + }; }; }; buildConfigurationList = BAB24F291DB5D45E00867756 /* Build configuration list for PBXProject "BPSampleApp" */; @@ -514,6 +610,8 @@ BA9C2DD11DD7F182007CB967 /* BPSampleAppFatalErrorTests */, BAFCCA5C1E36DC2000E33C31 /* BPSampleAppUITests */, E4F8A33D26F3B1AD00FE1267 /* BPSampleAppNewTests */, + FBBBD8F229A06B54002B9115 /* BPLogicTests */, + FBD772B32A33E15D0098CEFD /* BPPassingLogicTests */, ); }; /* End PBXProject section */ @@ -579,6 +677,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + FBBBD8FA29A06B54002B9115 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FBD772B82A33E15D0098CEFD /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -651,6 +763,24 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + FBBBD8F529A06B54002B9115 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FBBBD90029A06B7B002B9115 /* BPLogicTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FBD772B42A33E15D0098CEFD /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FBD772C52A3483310098CEFD /* SwiftLogicTests.swift in Sources */, + FBD772B52A33E15D0098CEFD /* BPPassingLogicTests.m in Sources */, + FBD772C72A378F440098CEFD /* BPBulkLogicTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -891,6 +1021,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + "EXCLUDED_ARCHS[sdk=*]" = arm64; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -1057,6 +1188,70 @@ }; name = Release; }; + FBBBD8FC29A06B54002B9115 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = 57Y47U492U; + INFOPLIST_FILE = "BPSampleAppTests-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = LI.BPSampleAppTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "BPSampleAppTests/BPSampleAppTests-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.2; + }; + name = Debug; + }; + FBBBD8FD29A06B54002B9115 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = 57Y47U492U; + INFOPLIST_FILE = "BPSampleAppTests-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = LI.BPSampleAppTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "BPSampleAppTests/BPSampleAppTests-Bridging-Header.h"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.2; + }; + name = Release; + }; + FBD772BA2A33E15D0098CEFD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = 57Y47U492U; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; + INFOPLIST_FILE = "BPLogicTests-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = LI.BPSampleAppTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "BPSampleAppTests/BPSampleAppTests-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.2; + }; + name = Debug; + }; + FBD772BB2A33E15D0098CEFD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = 57Y47U492U; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; + INFOPLIST_FILE = "BPLogicTests-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = LI.BPSampleAppTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "BPSampleAppTests/BPSampleAppTests-Bridging-Header.h"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.2; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1141,6 +1336,24 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + FBBBD8FB29A06B54002B9115 /* Build configuration list for PBXNativeTarget "BPLogicTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + FBBBD8FC29A06B54002B9115 /* Debug */, + FBBBD8FD29A06B54002B9115 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + FBD772B92A33E15D0098CEFD /* Build configuration list for PBXNativeTarget "BPPassingLogicTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + FBD772BA2A33E15D0098CEFD /* Debug */, + FBD772BB2A33E15D0098CEFD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = BAB24F261DB5D45E00867756 /* Project object */; diff --git a/BPSampleApp/BPSampleApp.xcodeproj/xcshareddata/xcschemes/BPLogicTests.xcscheme b/BPSampleApp/BPSampleApp.xcodeproj/xcshareddata/xcschemes/BPLogicTests.xcscheme new file mode 100644 index 00000000..03201efc --- /dev/null +++ b/BPSampleApp/BPSampleApp.xcodeproj/xcshareddata/xcschemes/BPLogicTests.xcscheme @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/BPSampleApp/BPSampleApp.xcodeproj/xcshareddata/xcschemes/BPPassingLogicTests.xcscheme b/BPSampleApp/BPSampleApp.xcodeproj/xcshareddata/xcschemes/BPPassingLogicTests.xcscheme new file mode 100644 index 00000000..5625c7da --- /dev/null +++ b/BPSampleApp/BPSampleApp.xcodeproj/xcshareddata/xcschemes/BPPassingLogicTests.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BPSampleApp/BPSampleApp.xcodeproj/xcshareddata/xcschemes/BPSampleApp.xcscheme b/BPSampleApp/BPSampleApp.xcodeproj/xcshareddata/xcschemes/BPSampleApp.xcscheme index 6028fa6b..86fbfb16 100644 --- a/BPSampleApp/BPSampleApp.xcodeproj/xcshareddata/xcschemes/BPSampleApp.xcscheme +++ b/BPSampleApp/BPSampleApp.xcodeproj/xcshareddata/xcschemes/BPSampleApp.xcscheme @@ -104,6 +104,34 @@ ReferencedContainer = "container:BPSampleApp.xcodeproj"> + + + + + + + + + + + + + + + + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/BPSampleApp/LogicTests/BPLogicTests/BPLogicTests.m b/BPSampleApp/LogicTests/BPLogicTests/BPLogicTests.m new file mode 100644 index 00000000..3618c788 --- /dev/null +++ b/BPSampleApp/LogicTests/BPLogicTests/BPLogicTests.m @@ -0,0 +1,81 @@ +// Copyright 2016 LinkedIn Corporation +// Licensed under the BSD 2-Clause License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://opensource.org/licenses/BSD-2-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +#import + +@interface BPLogicTests : XCTestCase + +@end + +@implementation BPLogicTests + +- (void)testPassingLogicTest1 { + XCTAssert(YES); +} + +- (void)testPassingLogicTest2 { + XCTAssert(YES); +} + +- (void)testFailingLogicTest { + XCTAssert(NO); +} + +/* + This failure should be recognized as a test failure + in the xctest logs, and testing should be able to continue. + */ +- (void)testCrashTestCaseLogicTest { + NSLog(@"BPLogicTests - FORCING TEST EXECUTION CRASH."); + NSObject *unused = @[][666]; +} + +/* + This failure will cause the whole execution to fail, and + requires separate special handling. + */ +- (void)testCrashExecutionLogicTest { + NSLog(@"BPLogicTests - FORCING SIMULATOR CRASH."); + char *p = NULL; + strcpy(p, "I know this will crash my app"); +} + +- (void)testStuckLogicTest { + NSLog(@"BPLogicTests - FORCING TEST TIMEOUT"); + while(1) { + sleep(10); + } +} + +- (void)testSlowLogicTest { + NSLog(@"BPLogicTests - FORCING TEST TIMEOUT"); + while(1) { + NSLog(@"Look I'm trying, but to no avail!"); + sleep(1); + } +} + +// The below should not timeout when run in succession + +- (void)testOneSecondTest1 { + sleep(1); + XCTAssert(YES); +} + +- (void)testOneSecondTest2 { + sleep(1); + XCTAssert(YES); +} + +- (void)testOneSecondTest3 { + sleep(1); + XCTAssert(YES); +} + +@end diff --git a/BPSampleApp/LogicTests/BPPassingLogicTests/BPBulkLogicTests.m b/BPSampleApp/LogicTests/BPPassingLogicTests/BPBulkLogicTests.m new file mode 100644 index 00000000..dab12a07 --- /dev/null +++ b/BPSampleApp/LogicTests/BPPassingLogicTests/BPBulkLogicTests.m @@ -0,0 +1,219 @@ +// Copyright 2016 LinkedIn Corporation +// Licensed under the BSD 2-Clause License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://opensource.org/licenses/BSD-2-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +#import + +@interface BPBulkLogicTests : XCTestCase + +@end + +@implementation BPBulkLogicTests + +- (void)testCase000 { XCTAssert(YES);} +- (void)testCase001 { XCTAssert(YES);} +- (void)testCase002 { XCTAssert(YES);} +- (void)testCase003 { XCTAssert(YES);} +- (void)testCase004 { XCTAssert(YES);} +- (void)testCase005 { XCTAssert(YES);} +- (void)testCase006 { XCTAssert(YES);} +- (void)testCase007 { XCTAssert(YES);} +- (void)testCase008 { XCTAssert(YES);} +- (void)testCase009 { XCTAssert(YES);} +- (void)testCase010 { XCTAssert(YES);} +- (void)testCase011 { XCTAssert(YES);} +- (void)testCase012 { XCTAssert(YES);} +- (void)testCase013 { XCTAssert(YES);} +- (void)testCase014 { XCTAssert(YES);} +- (void)testCase015 { XCTAssert(YES);} +- (void)testCase016 { XCTAssert(YES);} +- (void)testCase017 { XCTAssert(YES);} +- (void)testCase018 { XCTAssert(YES);} +- (void)testCase019 { XCTAssert(YES);} +- (void)testCase020 { XCTAssert(YES);} +- (void)testCase021 { XCTAssert(YES);} +- (void)testCase022 { XCTAssert(YES);} +- (void)testCase023 { XCTAssert(YES);} +- (void)testCase024 { XCTAssert(YES);} +- (void)testCase025 { XCTAssert(YES);} +- (void)testCase026 { XCTAssert(YES);} +- (void)testCase027 { XCTAssert(YES);} +- (void)testCase028 { XCTAssert(YES);} +- (void)testCase029 { XCTAssert(YES);} +- (void)testCase030 { XCTAssert(YES);} +- (void)testCase031 { XCTAssert(YES);} +- (void)testCase032 { XCTAssert(YES);} +- (void)testCase033 { XCTAssert(YES);} +- (void)testCase034 { XCTAssert(YES);} +- (void)testCase035 { XCTAssert(YES);} +- (void)testCase036 { XCTAssert(YES);} +- (void)testCase037 { XCTAssert(YES);} +- (void)testCase038 { XCTAssert(YES);} +- (void)testCase039 { XCTAssert(YES);} +- (void)testCase040 { XCTAssert(YES);} +- (void)testCase041 { XCTAssert(YES);} +- (void)testCase042 { XCTAssert(YES);} +- (void)testCase043 { XCTAssert(YES);} +- (void)testCase044 { XCTAssert(YES);} +- (void)testCase045 { XCTAssert(YES);} +- (void)testCase046 { XCTAssert(YES);} +- (void)testCase047 { XCTAssert(YES);} +- (void)testCase048 { XCTAssert(YES);} +- (void)testCase049 { XCTAssert(YES);} +- (void)testCase050 { XCTAssert(YES);} +- (void)testCase051 { XCTAssert(YES);} +- (void)testCase052 { XCTAssert(YES);} +- (void)testCase053 { XCTAssert(YES);} +- (void)testCase054 { XCTAssert(YES);} +- (void)testCase055 { XCTAssert(YES);} +- (void)testCase056 { XCTAssert(YES);} +- (void)testCase057 { XCTAssert(YES);} +- (void)testCase058 { XCTAssert(YES);} +- (void)testCase059 { XCTAssert(YES);} +- (void)testCase060 { XCTAssert(YES);} +- (void)testCase061 { XCTAssert(YES);} +- (void)testCase062 { XCTAssert(YES);} +- (void)testCase063 { XCTAssert(YES);} +- (void)testCase064 { XCTAssert(YES);} +- (void)testCase065 { XCTAssert(YES);} +- (void)testCase066 { XCTAssert(YES);} +- (void)testCase067 { XCTAssert(YES);} +- (void)testCase068 { XCTAssert(YES);} +- (void)testCase069 { XCTAssert(YES);} +- (void)testCase070 { XCTAssert(YES);} +- (void)testCase071 { XCTAssert(YES);} +- (void)testCase072 { XCTAssert(YES);} +- (void)testCase073 { XCTAssert(YES);} +- (void)testCase074 { XCTAssert(YES);} +- (void)testCase075 { XCTAssert(YES);} +- (void)testCase076 { XCTAssert(YES);} +- (void)testCase077 { XCTAssert(YES);} +- (void)testCase078 { XCTAssert(YES);} +- (void)testCase079 { XCTAssert(YES);} +- (void)testCase080 { XCTAssert(YES);} +- (void)testCase081 { XCTAssert(YES);} +- (void)testCase082 { XCTAssert(YES);} +- (void)testCase083 { XCTAssert(YES);} +- (void)testCase084 { XCTAssert(YES);} +- (void)testCase085 { XCTAssert(YES);} +- (void)testCase086 { XCTAssert(YES);} +- (void)testCase087 { XCTAssert(YES);} +- (void)testCase088 { XCTAssert(YES);} +- (void)testCase089 { XCTAssert(YES);} +- (void)testCase090 { XCTAssert(YES);} +- (void)testCase091 { XCTAssert(YES);} +- (void)testCase092 { XCTAssert(YES);} +- (void)testCase093 { XCTAssert(YES);} +- (void)testCase094 { XCTAssert(YES);} +- (void)testCase095 { XCTAssert(YES);} +- (void)testCase096 { XCTAssert(YES);} +- (void)testCase097 { XCTAssert(YES);} +- (void)testCase098 { XCTAssert(YES);} +- (void)testCase099 { XCTAssert(YES);} +- (void)testCase100 { XCTAssert(YES);} +- (void)testCase101 { XCTAssert(YES);} +- (void)testCase102 { XCTAssert(YES);} +- (void)testCase103 { XCTAssert(YES);} +- (void)testCase104 { XCTAssert(YES);} +- (void)testCase105 { XCTAssert(YES);} +- (void)testCase106 { XCTAssert(YES);} +- (void)testCase107 { XCTAssert(YES);} +- (void)testCase108 { XCTAssert(YES);} +- (void)testCase109 { XCTAssert(YES);} +- (void)testCase110 { XCTAssert(YES);} +- (void)testCase111 { XCTAssert(YES);} +- (void)testCase112 { XCTAssert(YES);} +- (void)testCase113 { XCTAssert(YES);} +- (void)testCase114 { XCTAssert(YES);} +- (void)testCase115 { XCTAssert(YES);} +- (void)testCase116 { XCTAssert(YES);} +- (void)testCase117 { XCTAssert(YES);} +- (void)testCase118 { XCTAssert(YES);} +- (void)testCase119 { XCTAssert(YES);} +- (void)testCase120 { XCTAssert(YES);} +- (void)testCase121 { XCTAssert(YES);} +- (void)testCase122 { XCTAssert(YES);} +- (void)testCase123 { XCTAssert(YES);} +- (void)testCase124 { XCTAssert(YES);} +- (void)testCase125 { XCTAssert(YES);} +- (void)testCase126 { XCTAssert(YES);} +- (void)testCase127 { XCTAssert(YES);} +- (void)testCase128 { XCTAssert(YES);} +- (void)testCase129 { XCTAssert(YES);} +- (void)testCase130 { XCTAssert(YES);} +- (void)testCase131 { XCTAssert(YES);} +- (void)testCase132 { XCTAssert(YES);} +- (void)testCase133 { XCTAssert(YES);} +- (void)testCase134 { XCTAssert(YES);} +- (void)testCase135 { XCTAssert(YES);} +- (void)testCase136 { XCTAssert(YES);} +- (void)testCase137 { XCTAssert(YES);} +- (void)testCase138 { XCTAssert(YES);} +- (void)testCase139 { XCTAssert(YES);} +- (void)testCase140 { XCTAssert(YES);} +- (void)testCase141 { XCTAssert(YES);} +- (void)testCase142 { XCTAssert(YES);} +- (void)testCase143 { XCTAssert(YES);} +- (void)testCase144 { XCTAssert(YES);} +- (void)testCase145 { XCTAssert(YES);} +- (void)testCase146 { XCTAssert(YES);} +- (void)testCase147 { XCTAssert(YES);} +- (void)testCase148 { XCTAssert(YES);} +- (void)testCase149 { XCTAssert(YES);} +- (void)testCase150 { XCTAssert(YES);} +- (void)testCase151 { XCTAssert(YES);} +- (void)testCase152 { XCTAssert(YES);} +- (void)testCase153 { XCTAssert(YES);} +- (void)testCase154 { XCTAssert(YES);} +- (void)testCase155 { XCTAssert(YES);} +- (void)testCase156 { XCTAssert(YES);} +- (void)testCase157 { XCTAssert(YES);} +- (void)testCase158 { XCTAssert(YES);} +- (void)testCase159 { XCTAssert(YES);} +- (void)testCase160 { XCTAssert(YES);} +- (void)testCase161 { XCTAssert(YES);} +- (void)testCase162 { XCTAssert(YES);} +- (void)testCase163 { XCTAssert(YES);} +- (void)testCase164 { XCTAssert(YES);} +- (void)testCase165 { XCTAssert(YES);} +- (void)testCase166 { XCTAssert(YES);} +- (void)testCase167 { XCTAssert(YES);} +- (void)testCase168 { XCTAssert(YES);} +- (void)testCase169 { XCTAssert(YES);} +- (void)testCase170 { XCTAssert(YES);} +- (void)testCase171 { XCTAssert(YES);} +- (void)testCase172 { XCTAssert(YES);} +- (void)testCase173 { XCTAssert(YES);} +- (void)testCase174 { XCTAssert(YES);} +- (void)testCase175 { XCTAssert(YES);} +- (void)testCase176 { XCTAssert(YES);} +- (void)testCase177 { XCTAssert(YES);} +- (void)testCase178 { XCTAssert(YES);} +- (void)testCase179 { XCTAssert(YES);} +- (void)testCase180 { XCTAssert(YES);} +- (void)testCase181 { XCTAssert(YES);} +- (void)testCase182 { XCTAssert(YES);} +- (void)testCase183 { XCTAssert(YES);} +- (void)testCase184 { XCTAssert(YES);} +- (void)testCase185 { XCTAssert(YES);} +- (void)testCase186 { XCTAssert(YES);} +- (void)testCase187 { XCTAssert(YES);} +- (void)testCase188 { XCTAssert(YES);} +- (void)testCase189 { XCTAssert(YES);} +- (void)testCase190 { XCTAssert(YES);} +- (void)testCase191 { XCTAssert(YES);} +- (void)testCase192 { XCTAssert(YES);} +- (void)testCase193 { XCTAssert(YES);} +- (void)testCase194 { XCTAssert(YES);} +- (void)testCase195 { XCTAssert(YES);} +- (void)testCase196 { XCTAssert(YES);} +- (void)testCase197 { XCTAssert(YES);} +- (void)testCase198 { XCTAssert(YES);} +- (void)testCase199 { XCTAssert(YES);} + +@end diff --git a/BPSampleApp/LogicTests/BPPassingLogicTests/BPPassingLogicTests.m b/BPSampleApp/LogicTests/BPPassingLogicTests/BPPassingLogicTests.m new file mode 100644 index 00000000..ba270a0a --- /dev/null +++ b/BPSampleApp/LogicTests/BPPassingLogicTests/BPPassingLogicTests.m @@ -0,0 +1,34 @@ +// Copyright 2016 LinkedIn Corporation +// Licensed under the BSD 2-Clause License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://opensource.org/licenses/BSD-2-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +#import + +@interface BPPassingLogicTests : XCTestCase + +@end + +@implementation BPPassingLogicTests + +- (void)testPassingLogicTest1 { + XCTAssert(YES); +} + +- (void)testPassingLogicTest2 { + XCTAssert(YES); +} + +- (void)testPassingLogicTest3 { + XCTAssert(YES); +} + +- (void)testPassingLogicTest4 { + XCTAssert(YES); +} + +@end diff --git a/BPSampleApp/LogicTests/BPPassingLogicTests/SwiftLogicTests.swift b/BPSampleApp/LogicTests/BPPassingLogicTests/SwiftLogicTests.swift new file mode 100644 index 00000000..09779d66 --- /dev/null +++ b/BPSampleApp/LogicTests/BPPassingLogicTests/SwiftLogicTests.swift @@ -0,0 +1,26 @@ +// Copyright 2016 LinkedIn Corporation +// Licensed under the BSD 2-Clause License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://opensource.org/licenses/BSD-2-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +import XCTest + +final class SwiftLogicTests: XCTestCase { + + func testPassingLogicTest1() { + XCTAssert(true) + } + + func testPassingLogicTest2() { + XCTAssert(true) + } + + func testPassingLogicTest3() { + XCTAssert(true) + } + +} diff --git a/BPSampleApp/LogicTests/Info.plist b/BPSampleApp/LogicTests/Info.plist new file mode 100644 index 00000000..6c6c23c4 --- /dev/null +++ b/BPSampleApp/LogicTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/BPTestInspector/BPTestCaseInfo.h b/BPTestInspector/BPTestCaseInfo.h new file mode 100644 index 00000000..462314ff --- /dev/null +++ b/BPTestInspector/BPTestCaseInfo.h @@ -0,0 +1,42 @@ +// Copyright 2016 LinkedIn Corporation +// Licensed under the BSD 2-Clause License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://opensource.org/licenses/BSD-2-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + This class is a basic representation of an XCTestCase, with all the information required to + add the test to an include/exclude list when specifying what tests to run in an xctest execution. + + Notably, this class conforms to NSSecureCoding so that it can be encoded, piped out to a + parent execution, and then decoded. + */ +@interface BPTestCaseInfo : NSObject + +@property (nonatomic, copy, nonnull, readonly) NSString *className; +@property (nonatomic, copy, nonnull, readonly) NSString *methodName; + +/** + The name of the test, formatted correctly for XCTest (regardless of Obj-C vs Swift) + + @example `MyTestClass/MyTestCase` in Obj-C, or `MyTestModule.MyTestClass/MyTestCase` in Swift + */ +@property (nonatomic, copy, nonnull, readonly) NSString *standardizedFullName; +/** + The name of the test, formatted according to how BP consumers expect to list the test + in the opt-in or skip lists, or how they should be displayed in the generated test results. + + @example `MyTestClass/MyTestCase` in Obj-C, or `MyTestClass/MyTestCase()` in Swift + */ +@property (nonatomic, copy, nonnull, readonly) NSString *prettifiedFullName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/BPTestInspector/BPTestCaseInfo.m b/BPTestInspector/BPTestCaseInfo.m new file mode 100644 index 00000000..ab5c4c42 --- /dev/null +++ b/BPTestInspector/BPTestCaseInfo.m @@ -0,0 +1,81 @@ +// Copyright 2016 LinkedIn Corporation +// Licensed under the BSD 2-Clause License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://opensource.org/licenses/BSD-2-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +#import "BPTestCaseInfo+Internal.h" +#import "XCTestCase.h" + +@implementation BPTestCaseInfo + +#pragma mark - Initializers + +- (instancetype)initWithClassName:(NSString *)className + methodName:(NSString *)methodName { + if (self = [super init]) { + _className = [className copy]; + _methodName = [methodName copy]; + } + return self; +} + ++ (instancetype)infoFromTestCase:(XCTestCase *)testCase { + NSString *className = NSStringFromClass(testCase.class); + NSString *methodName = [testCase respondsToSelector:@selector(languageAgnosticTestMethodName)] ? [testCase languageAgnosticTestMethodName] : NSStringFromSelector([testCase.invocation selector]); + return [[BPTestCaseInfo alloc] initWithClassName:className methodName:methodName]; +} + +#pragma mark - Properties + +- (NSString *)standardizedFullName { + return [NSString stringWithFormat:@"%@/%@", self.className, self.methodName]; +} + +- (NSString *)prettifiedFullName { + /* + If the class name contains a `.`, this is a Swift test case, and needs extra formatting. + Otherwise, we in Obj-C, it's unchanged from the `standardizedFullName` + */ + NSArray *classComponents = [self.className componentsSeparatedByString:@"."]; + if (classComponents.count < 2) { + return self.standardizedFullName; + } + return [NSString stringWithFormat:@"%@/%@()", classComponents[1], self.methodName]; +} + +#pragma mark - Overrides + +- (NSString *)description { + return self.standardizedFullName; +} + +- (BOOL)isEqual:(id)object { + if (!object || ![object isMemberOfClass:BPTestCaseInfo.class]) { + return NO; + } + BPTestCaseInfo *other = (BPTestCaseInfo *)object; + return [self.className isEqual:other.className] && [self.methodName isEqual:other.methodName]; +} + +#pragma mark - NSSecureCoding + ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (void)encodeWithCoder:(nonnull NSCoder *)coder { + [coder encodeObject:self.className forKey:@"className"]; + [coder encodeObject:self.methodName forKey:@"methodName"]; +} + +- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder { + return [self initWithClassName:[coder decodeObjectOfClass:NSString.class forKey:@"className"] + methodName:[coder decodeObjectOfClass:NSString.class forKey:@"methodName"]]; + +} + +@end diff --git a/BPTestInspector/BPTestInspector.xcodeproj/project.pbxproj b/BPTestInspector/BPTestInspector.xcodeproj/project.pbxproj new file mode 100644 index 00000000..30305b6d --- /dev/null +++ b/BPTestInspector/BPTestInspector.xcodeproj/project.pbxproj @@ -0,0 +1,443 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + FB21F07F2A62286400682AC7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = FB21F07E2A62286400682AC7 /* main.m */; }; + FB21F0862A62297F00682AC7 /* BPLoggingUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F0812A62297F00682AC7 /* BPLoggingUtils.h */; }; + FB21F0872A62297F00682AC7 /* BPTestCaseInfo+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F0822A62297F00682AC7 /* BPTestCaseInfo+Internal.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FB21F0882A62297F00682AC7 /* BPXCTestUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = FB21F0832A62297F00682AC7 /* BPXCTestUtils.m */; }; + FB21F0892A62297F00682AC7 /* BPLoggingUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = FB21F0842A62297F00682AC7 /* BPLoggingUtils.m */; }; + FB21F08A2A62297F00682AC7 /* BPXCTestUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F0852A62297F00682AC7 /* BPXCTestUtils.h */; }; + FB21F08F2A62298B00682AC7 /* BPTestCaseInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = FB21F08B2A62298A00682AC7 /* BPTestCaseInfo.m */; }; + FB21F0902A62298B00682AC7 /* BPTestCaseInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = FB21F08B2A62298A00682AC7 /* BPTestCaseInfo.m */; }; + FB21F0912A62298B00682AC7 /* BPTestInspectorConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F08C2A62298A00682AC7 /* BPTestInspectorConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FB21F0922A62298B00682AC7 /* BPTestInspectorConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F08C2A62298A00682AC7 /* BPTestInspectorConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FB21F0932A62298B00682AC7 /* BPTestCaseInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F08D2A62298B00682AC7 /* BPTestCaseInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FB21F0942A62298B00682AC7 /* BPTestCaseInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F08D2A62298B00682AC7 /* BPTestCaseInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FB21F0952A62298B00682AC7 /* BPTestInspectorConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = FB21F08E2A62298B00682AC7 /* BPTestInspectorConstants.m */; }; + FB21F0962A62298B00682AC7 /* BPTestInspectorConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = FB21F08E2A62298B00682AC7 /* BPTestInspectorConstants.m */; }; + FB21F0A82A622A0200682AC7 /* XCTestObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F0982A622A0200682AC7 /* XCTestObserver.h */; }; + FB21F0A92A622A0200682AC7 /* XCTestSuiteRun.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F0992A622A0200682AC7 /* XCTestSuiteRun.h */; }; + FB21F0AA2A622A0200682AC7 /* XCTestSuite.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F09A2A622A0200682AC7 /* XCTestSuite.h */; }; + FB21F0AB2A622A0200682AC7 /* CDStructures.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F09B2A622A0200682AC7 /* CDStructures.h */; }; + FB21F0AC2A622A0200682AC7 /* XCTestLog.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F09C2A622A0200682AC7 /* XCTestLog.h */; }; + FB21F0AD2A622A0200682AC7 /* XCTest.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F09D2A622A0200682AC7 /* XCTest.h */; }; + FB21F0AE2A622A0200682AC7 /* XCTestRun.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F09E2A622A0200682AC7 /* XCTestRun.h */; }; + FB21F0AF2A622A0200682AC7 /* XCTestCase.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F09F2A622A0200682AC7 /* XCTestCase.h */; }; + FB21F0B02A622A0200682AC7 /* XCTActivity-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F0A12A622A0200682AC7 /* XCTActivity-Protocol.h */; }; + FB21F0B12A622A0200682AC7 /* XCTIssueHandling-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F0A22A622A0200682AC7 /* XCTIssueHandling-Protocol.h */; }; + FB21F0B22A622A0200682AC7 /* XCTestObservation-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F0A32A622A0200682AC7 /* XCTestObservation-Protocol.h */; }; + FB21F0B32A622A0200682AC7 /* _XCTestObservationPrivate-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F0A42A622A0200682AC7 /* _XCTestObservationPrivate-Protocol.h */; }; + FB21F0B42A622A0200682AC7 /* XCTWaiterDelegate-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F0A52A622A0200682AC7 /* XCTWaiterDelegate-Protocol.h */; }; + FB21F0B52A622A0200682AC7 /* NSObject-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F0A62A622A0200682AC7 /* NSObject-Protocol.h */; }; + FB21F0B62A622A0200682AC7 /* _XCTestObservationInternal-Protocol.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F0A72A622A0200682AC7 /* _XCTestObservationInternal-Protocol.h */; }; + FB21F0B92A622AA000682AC7 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB21F0B82A622AA000682AC7 /* XCTest.framework */; }; + FB21F0DF2A65106E00682AC7 /* BPTestCaseInfo+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = FB21F0822A62297F00682AC7 /* BPTestCaseInfo+Internal.h */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + AA017F411BD7776B00F45E9D /* libBPTestInspector.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libBPTestInspector.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; + AA56EAE21EEF08720062C2BC /* libBPMacTestInspector.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libBPMacTestInspector.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; + EED62ED820D12209006E86E5 /* BPMacTestInspector.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = BPMacTestInspector.xcconfig; sourceTree = ""; }; + EED62ED920D12209006E86E5 /* BPTestInspector.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = BPTestInspector.xcconfig; sourceTree = ""; }; + FB21F07E2A62286400682AC7 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + FB21F0812A62297F00682AC7 /* BPLoggingUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BPLoggingUtils.h; sourceTree = ""; }; + FB21F0822A62297F00682AC7 /* BPTestCaseInfo+Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BPTestCaseInfo+Internal.h"; sourceTree = ""; }; + FB21F0832A62297F00682AC7 /* BPXCTestUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BPXCTestUtils.m; sourceTree = ""; }; + FB21F0842A62297F00682AC7 /* BPLoggingUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BPLoggingUtils.m; sourceTree = ""; }; + FB21F0852A62297F00682AC7 /* BPXCTestUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BPXCTestUtils.h; sourceTree = ""; }; + FB21F08B2A62298A00682AC7 /* BPTestCaseInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BPTestCaseInfo.m; sourceTree = ""; }; + FB21F08C2A62298A00682AC7 /* BPTestInspectorConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BPTestInspectorConstants.h; sourceTree = ""; }; + FB21F08D2A62298B00682AC7 /* BPTestCaseInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BPTestCaseInfo.h; sourceTree = ""; }; + FB21F08E2A62298B00682AC7 /* BPTestInspectorConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BPTestInspectorConstants.m; sourceTree = ""; }; + FB21F0982A622A0200682AC7 /* XCTestObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCTestObserver.h; sourceTree = ""; }; + FB21F0992A622A0200682AC7 /* XCTestSuiteRun.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCTestSuiteRun.h; sourceTree = ""; }; + FB21F09A2A622A0200682AC7 /* XCTestSuite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCTestSuite.h; sourceTree = ""; }; + FB21F09B2A622A0200682AC7 /* CDStructures.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDStructures.h; sourceTree = ""; }; + FB21F09C2A622A0200682AC7 /* XCTestLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCTestLog.h; sourceTree = ""; }; + FB21F09D2A622A0200682AC7 /* XCTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCTest.h; sourceTree = ""; }; + FB21F09E2A622A0200682AC7 /* XCTestRun.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCTestRun.h; sourceTree = ""; }; + FB21F09F2A622A0200682AC7 /* XCTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCTestCase.h; sourceTree = ""; }; + FB21F0A12A622A0200682AC7 /* XCTActivity-Protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCTActivity-Protocol.h"; sourceTree = ""; }; + FB21F0A22A622A0200682AC7 /* XCTIssueHandling-Protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCTIssueHandling-Protocol.h"; sourceTree = ""; }; + FB21F0A32A622A0200682AC7 /* XCTestObservation-Protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCTestObservation-Protocol.h"; sourceTree = ""; }; + FB21F0A42A622A0200682AC7 /* _XCTestObservationPrivate-Protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "_XCTestObservationPrivate-Protocol.h"; sourceTree = ""; }; + FB21F0A52A622A0200682AC7 /* XCTWaiterDelegate-Protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCTWaiterDelegate-Protocol.h"; sourceTree = ""; }; + FB21F0A62A622A0200682AC7 /* NSObject-Protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject-Protocol.h"; sourceTree = ""; }; + FB21F0A72A622A0200682AC7 /* _XCTestObservationInternal-Protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "_XCTestObservationInternal-Protocol.h"; sourceTree = ""; }; + FB21F0B82A622AA000682AC7 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + AA017F3E1BD7776B00F45E9D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + FB21F0B92A622AA000682AC7 /* XCTest.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AA56EADF1EEF08720062C2BC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + AA017F381BD7776B00F45E9D = { + isa = PBXGroup; + children = ( + FB21F08D2A62298B00682AC7 /* BPTestCaseInfo.h */, + FB21F08B2A62298A00682AC7 /* BPTestCaseInfo.m */, + FB21F08C2A62298A00682AC7 /* BPTestInspectorConstants.h */, + FB21F08E2A62298B00682AC7 /* BPTestInspectorConstants.m */, + FB21F0802A62296A00682AC7 /* Internal */, + EED62ED420D1212D006E86E5 /* Configs */, + AA017F421BD7776B00F45E9D /* Products */, + FB21F0B72A622AA000682AC7 /* Frameworks */, + ); + sourceTree = ""; + }; + AA017F421BD7776B00F45E9D /* Products */ = { + isa = PBXGroup; + children = ( + AA017F411BD7776B00F45E9D /* libBPTestInspector.dylib */, + AA56EAE21EEF08720062C2BC /* libBPMacTestInspector.dylib */, + ); + name = Products; + sourceTree = ""; + }; + EED62ED420D1212D006E86E5 /* Configs */ = { + isa = PBXGroup; + children = ( + EED62ED820D12209006E86E5 /* BPMacTestInspector.xcconfig */, + EED62ED920D12209006E86E5 /* BPTestInspector.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + FB21F0802A62296A00682AC7 /* Internal */ = { + isa = PBXGroup; + children = ( + FB21F07E2A62286400682AC7 /* main.m */, + FB21F0812A62297F00682AC7 /* BPLoggingUtils.h */, + FB21F0842A62297F00682AC7 /* BPLoggingUtils.m */, + FB21F0822A62297F00682AC7 /* BPTestCaseInfo+Internal.h */, + FB21F0852A62297F00682AC7 /* BPXCTestUtils.h */, + FB21F0832A62297F00682AC7 /* BPXCTestUtils.m */, + FB21F0972A622A0200682AC7 /* PrivateHeaders */, + ); + path = Internal; + sourceTree = ""; + }; + FB21F0972A622A0200682AC7 /* PrivateHeaders */ = { + isa = PBXGroup; + children = ( + FB21F0982A622A0200682AC7 /* XCTestObserver.h */, + FB21F0992A622A0200682AC7 /* XCTestSuiteRun.h */, + FB21F09A2A622A0200682AC7 /* XCTestSuite.h */, + FB21F09B2A622A0200682AC7 /* CDStructures.h */, + FB21F09C2A622A0200682AC7 /* XCTestLog.h */, + FB21F09D2A622A0200682AC7 /* XCTest.h */, + FB21F09E2A622A0200682AC7 /* XCTestRun.h */, + FB21F09F2A622A0200682AC7 /* XCTestCase.h */, + FB21F0A02A622A0200682AC7 /* Protocols */, + ); + path = PrivateHeaders; + sourceTree = ""; + }; + FB21F0A02A622A0200682AC7 /* Protocols */ = { + isa = PBXGroup; + children = ( + FB21F0A12A622A0200682AC7 /* XCTActivity-Protocol.h */, + FB21F0A22A622A0200682AC7 /* XCTIssueHandling-Protocol.h */, + FB21F0A32A622A0200682AC7 /* XCTestObservation-Protocol.h */, + FB21F0A42A622A0200682AC7 /* _XCTestObservationPrivate-Protocol.h */, + FB21F0A52A622A0200682AC7 /* XCTWaiterDelegate-Protocol.h */, + FB21F0A62A622A0200682AC7 /* NSObject-Protocol.h */, + FB21F0A72A622A0200682AC7 /* _XCTestObservationInternal-Protocol.h */, + ); + path = Protocols; + sourceTree = ""; + }; + FB21F0B72A622AA000682AC7 /* Frameworks */ = { + isa = PBXGroup; + children = ( + FB21F0B82A622AA000682AC7 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + AA017F3F1BD7776B00F45E9D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + FB21F0B12A622A0200682AC7 /* XCTIssueHandling-Protocol.h in Headers */, + FB21F0AA2A622A0200682AC7 /* XCTestSuite.h in Headers */, + FB21F0AB2A622A0200682AC7 /* CDStructures.h in Headers */, + FB21F0B42A622A0200682AC7 /* XCTWaiterDelegate-Protocol.h in Headers */, + FB21F08A2A62297F00682AC7 /* BPXCTestUtils.h in Headers */, + FB21F0A92A622A0200682AC7 /* XCTestSuiteRun.h in Headers */, + FB21F0A82A622A0200682AC7 /* XCTestObserver.h in Headers */, + FB21F0AE2A622A0200682AC7 /* XCTestRun.h in Headers */, + FB21F0862A62297F00682AC7 /* BPLoggingUtils.h in Headers */, + FB21F0B22A622A0200682AC7 /* XCTestObservation-Protocol.h in Headers */, + FB21F0AC2A622A0200682AC7 /* XCTestLog.h in Headers */, + FB21F0B52A622A0200682AC7 /* NSObject-Protocol.h in Headers */, + FB21F0B32A622A0200682AC7 /* _XCTestObservationPrivate-Protocol.h in Headers */, + FB21F0B62A622A0200682AC7 /* _XCTestObservationInternal-Protocol.h in Headers */, + FB21F0AF2A622A0200682AC7 /* XCTestCase.h in Headers */, + FB21F0932A62298B00682AC7 /* BPTestCaseInfo.h in Headers */, + FB21F0AD2A622A0200682AC7 /* XCTest.h in Headers */, + FB21F0B02A622A0200682AC7 /* XCTActivity-Protocol.h in Headers */, + FB21F0872A62297F00682AC7 /* BPTestCaseInfo+Internal.h in Headers */, + FB21F0912A62298B00682AC7 /* BPTestInspectorConstants.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AA56EAE01EEF08720062C2BC /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + FB21F0942A62298B00682AC7 /* BPTestCaseInfo.h in Headers */, + FB21F0DF2A65106E00682AC7 /* BPTestCaseInfo+Internal.h in Headers */, + FB21F0922A62298B00682AC7 /* BPTestInspectorConstants.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + AA017F401BD7776B00F45E9D /* BPTestInspector */ = { + isa = PBXNativeTarget; + buildConfigurationList = AA017F451BD7776B00F45E9D /* Build configuration list for PBXNativeTarget "BPTestInspector" */; + buildPhases = ( + AA017F3D1BD7776B00F45E9D /* Sources */, + AA017F3E1BD7776B00F45E9D /* Frameworks */, + AA017F3F1BD7776B00F45E9D /* Headers */, + FBD60B072B33FF48005A5642 /* Print TestInspector Path */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = BPTestInspector; + productName = Shimulator; + productReference = AA017F411BD7776B00F45E9D /* libBPTestInspector.dylib */; + productType = "com.apple.product-type.library.dynamic"; + }; + AA56EAE11EEF08720062C2BC /* BPMacTestInspector */ = { + isa = PBXNativeTarget; + buildConfigurationList = AA56EAEA1EEF08720062C2BC /* Build configuration list for PBXNativeTarget "BPMacTestInspector" */; + buildPhases = ( + AA56EADE1EEF08720062C2BC /* Sources */, + AA56EADF1EEF08720062C2BC /* Frameworks */, + AA56EAE01EEF08720062C2BC /* Headers */, + 71B9E0DC268C7A4600D40A91 /* Print MacTestInspector Path */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = BPMacTestInspector; + productName = Maculator; + productReference = AA56EAE21EEF08720062C2BC /* libBPMacTestInspector.dylib */; + productType = "com.apple.product-type.library.dynamic"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + AA017F391BD7776B00F45E9D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0700; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + AA017F401BD7776B00F45E9D = { + CreatedOnToolsVersion = 7.0.1; + }; + AA56EAE11EEF08720062C2BC = { + CreatedOnToolsVersion = 9.0; + }; + }; + }; + buildConfigurationList = AA017F3C1BD7776B00F45E9D /* Build configuration list for PBXProject "BPTestInspector" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = AA017F381BD7776B00F45E9D; + productRefGroup = AA017F421BD7776B00F45E9D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + AA017F401BD7776B00F45E9D /* BPTestInspector */, + AA56EAE11EEF08720062C2BC /* BPMacTestInspector */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXShellScriptBuildPhase section */ + 71B9E0DC268C7A4600D40A91 /* Print MacTestInspector Path */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Print MacTestInspector Path"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo MAC_TEST_INSPECTOR_DYLIB_PATH=$BUILT_PRODUCTS_DIR/$FULL_PRODUCT_NAME\n"; + showEnvVarsInLog = 0; + }; + FBD60B072B33FF48005A5642 /* Print TestInspector Path */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Print TestInspector Path"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo TEST_INSPECTOR_DYLIB_PATH=$BUILT_PRODUCTS_DIR/$FULL_PRODUCT_NAME\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + AA017F3D1BD7776B00F45E9D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FB21F0882A62297F00682AC7 /* BPXCTestUtils.m in Sources */, + FB21F08F2A62298B00682AC7 /* BPTestCaseInfo.m in Sources */, + FB21F07F2A62286400682AC7 /* main.m in Sources */, + FB21F0892A62297F00682AC7 /* BPLoggingUtils.m in Sources */, + FB21F0952A62298B00682AC7 /* BPTestInspectorConstants.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AA56EADE1EEF08720062C2BC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FB21F0962A62298B00682AC7 /* BPTestInspectorConstants.m in Sources */, + FB21F0902A62298B00682AC7 /* BPTestCaseInfo.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + AA017F431BD7776B00F45E9D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "-"; + GCC_OPTIMIZATION_LEVEL = 0; + }; + name = Debug; + }; + AA017F441BD7776B00F45E9D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "-"; + GCC_OPTIMIZATION_LEVEL = 0; + }; + name = Release; + }; + AA017F461BD7776B00F45E9D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EED62ED920D12209006E86E5 /* BPTestInspector.xcconfig */; + buildSettings = { + SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; + }; + name = Debug; + }; + AA017F471BD7776B00F45E9D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EED62ED920D12209006E86E5 /* BPTestInspector.xcconfig */; + buildSettings = { + SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; + }; + name = Release; + }; + AA56EAE81EEF08720062C2BC /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EED62ED820D12209006E86E5 /* BPMacTestInspector.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + AA56EAE91EEF08720062C2BC /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EED62ED820D12209006E86E5 /* BPMacTestInspector.xcconfig */; + buildSettings = { + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + AA017F3C1BD7776B00F45E9D /* Build configuration list for PBXProject "BPTestInspector" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AA017F431BD7776B00F45E9D /* Debug */, + AA017F441BD7776B00F45E9D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + AA017F451BD7776B00F45E9D /* Build configuration list for PBXNativeTarget "BPTestInspector" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AA017F461BD7776B00F45E9D /* Debug */, + AA017F471BD7776B00F45E9D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + AA56EAEA1EEF08720062C2BC /* Build configuration list for PBXNativeTarget "BPMacTestInspector" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AA56EAE81EEF08720062C2BC /* Debug */, + AA56EAE91EEF08720062C2BC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = AA017F391BD7776B00F45E9D /* Project object */; +} diff --git a/BPTestInspector/BPTestInspector.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/BPTestInspector/BPTestInspector.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..6291fb33 --- /dev/null +++ b/BPTestInspector/BPTestInspector.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/BPTestInspector/BPTestInspector.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/BPTestInspector/BPTestInspector.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/BPTestInspector/BPTestInspector.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/BPTestInspector/BPTestInspector.xcodeproj/xcshareddata/xcschemes/BPMacTestInspector.xcscheme b/BPTestInspector/BPTestInspector.xcodeproj/xcshareddata/xcschemes/BPMacTestInspector.xcscheme new file mode 100644 index 00000000..c56ec4cc --- /dev/null +++ b/BPTestInspector/BPTestInspector.xcodeproj/xcshareddata/xcschemes/BPMacTestInspector.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BPTestInspector/BPTestInspector.xcodeproj/xcshareddata/xcschemes/BPTestInspector.xcscheme b/BPTestInspector/BPTestInspector.xcodeproj/xcshareddata/xcschemes/BPTestInspector.xcscheme new file mode 100644 index 00000000..5e481eb8 --- /dev/null +++ b/BPTestInspector/BPTestInspector.xcodeproj/xcshareddata/xcschemes/BPTestInspector.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BPTestInspector/BPTestInspectorConstants.h b/BPTestInspector/BPTestInspectorConstants.h new file mode 100644 index 00000000..1c675c3f --- /dev/null +++ b/BPTestInspector/BPTestInspectorConstants.h @@ -0,0 +1,22 @@ +// Copyright 2016 LinkedIn Corporation +// Licensed under the BSD 2-Clause License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://opensource.org/licenses/BSD-2-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface BPTestInspectorConstants : NSObject + ++ (NSString *)dylibName; ++ (NSString *)testBundleEnvironmentKey; ++ (NSString *)outputPathEnvironmentKey; + +@end + +NS_ASSUME_NONNULL_END diff --git a/BPTestInspector/BPTestInspectorConstants.m b/BPTestInspector/BPTestInspectorConstants.m new file mode 100644 index 00000000..9026f145 --- /dev/null +++ b/BPTestInspector/BPTestInspectorConstants.m @@ -0,0 +1,26 @@ +// Copyright 2016 LinkedIn Corporation +// Licensed under the BSD 2-Clause License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://opensource.org/licenses/BSD-2-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +#import "BPTestInspectorConstants.h" + +@implementation BPTestInspectorConstants + ++ (NSString *)dylibName { + return @"libBPTestInspector.dylib"; +} + ++ (NSString *)testBundleEnvironmentKey { + return @"BP_XCTEST_WRAPPER__LOGIC_TEST_BUNDLE"; +} + ++ (NSString *)outputPathEnvironmentKey { + return @"BP_XCTEST_WRAPPER__TEST_CASE_OUTPUT"; +} + +@end diff --git a/BPTestInspector/Configs/BPMacTestInspector.xcconfig b/BPTestInspector/Configs/BPMacTestInspector.xcconfig new file mode 100644 index 00000000..371f806f --- /dev/null +++ b/BPTestInspector/Configs/BPMacTestInspector.xcconfig @@ -0,0 +1,8 @@ +CURRENT_PROJECT_VERSION = 1 +DYLIB_CURRENT_VERSION = 1 +DYLIB_COMPATIBILITY_VERSION = 1 +PRODUCT_NAME = BPMacTestInspector +EXECUTABLE_PREFIX = lib +SDKROOT = macosx +MACOSX_DEPLOYMENT_TARGET = 11.0 +COPY_PHASE_STRIP = NO diff --git a/BPTestInspector/Configs/BPTestInspector.xcconfig b/BPTestInspector/Configs/BPTestInspector.xcconfig new file mode 100644 index 00000000..964e63f0 --- /dev/null +++ b/BPTestInspector/Configs/BPTestInspector.xcconfig @@ -0,0 +1,9 @@ +CURRENT_PROJECT_VERSION = 1 +DYLIB_CURRENT_VERSION = 1 +DYLIB_COMPATIBILITY_VERSION = 1 +PRODUCT_NAME = BPTestInspector +EXECUTABLE_PREFIX = lib +SDKROOT = iphonesimulator +IPHONEOS_DEPLOYMENT_TARGET = 16.0 +COPY_PHASE_STRIP = NO +CODE_SIGN_IDENTITY = - // LTHROCKM - may need code signing for hosted tests diff --git a/BPTestInspector/Internal/BPLoggingUtils.h b/BPTestInspector/Internal/BPLoggingUtils.h new file mode 100644 index 00000000..65efdbb8 --- /dev/null +++ b/BPTestInspector/Internal/BPLoggingUtils.h @@ -0,0 +1,28 @@ +// Copyright 2016 LinkedIn Corporation +// Licensed under the BSD 2-Clause License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://opensource.org/licenses/BSD-2-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + A very basic logging wrapper to simplify logging info + errors within BPTestInspector + after it's been injected into an xctest execution. + + Currently, it just prints to console, though this could be improved in the future. + */ +@interface BPLoggingUtils : NSObject + ++ (void)log:(NSString *)message; + ++ (void)logError:(NSString *)errorMessage; + +@end + +NS_ASSUME_NONNULL_END diff --git a/BPTestInspector/Internal/BPLoggingUtils.m b/BPTestInspector/Internal/BPLoggingUtils.m new file mode 100644 index 00000000..a37bc9d1 --- /dev/null +++ b/BPTestInspector/Internal/BPLoggingUtils.m @@ -0,0 +1,22 @@ +// Copyright 2016 LinkedIn Corporation +// Licensed under the BSD 2-Clause License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://opensource.org/licenses/BSD-2-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +#import "BPLoggingUtils.h" + +@implementation BPLoggingUtils + ++ (void)log:(NSString *)message { + NSLog(@"[BPTestInspector] %@", message); +} + ++ (void)logError:(NSString *)errorMessage { + NSLog(@"[BPTestInspector] Error: %@", errorMessage); +} + +@end diff --git a/BPTestInspector/Internal/BPTestCaseInfo+Internal.h b/BPTestInspector/Internal/BPTestCaseInfo+Internal.h new file mode 100644 index 00000000..d4992cfc --- /dev/null +++ b/BPTestInspector/Internal/BPTestCaseInfo+Internal.h @@ -0,0 +1,27 @@ +// Copyright 2016 LinkedIn Corporation +// Licensed under the BSD 2-Clause License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://opensource.org/licenses/BSD-2-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +#import + +@class XCTestCase; + +NS_ASSUME_NONNULL_BEGIN + +@interface BPTestCaseInfo (Internal) + ++ (instancetype)infoFromTestCase:(XCTestCase *)testCase; + +#pragma mark - Testing + +- (instancetype)initWithClassName:(NSString *)className + methodName:(NSString *)methodName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/BPTestInspector/Internal/BPXCTestUtils.h b/BPTestInspector/Internal/BPXCTestUtils.h new file mode 100644 index 00000000..d658a615 --- /dev/null +++ b/BPTestInspector/Internal/BPXCTestUtils.h @@ -0,0 +1,28 @@ +// Copyright 2016 LinkedIn Corporation +// Licensed under the BSD 2-Clause License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://opensource.org/licenses/BSD-2-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface BPXCTestUtils : NSObject + +/** + Given a path to an .xctest test bundle, this method will encode all of the contained test info into an output file. + This information will be saved as data encoding for an `NSArray *`, stored + at the output path. + + @param bundlePath The path of the .xctest bundle + @param outputPath The path of the output file. + */ ++ (void)logAllTestsInBundleWithPath:(NSString *)bundlePath toFile:(NSString *)outputPath; + +@end + +NS_ASSUME_NONNULL_END diff --git a/BPTestInspector/Internal/BPXCTestUtils.m b/BPTestInspector/Internal/BPXCTestUtils.m new file mode 100644 index 00000000..0df05e25 --- /dev/null +++ b/BPTestInspector/Internal/BPXCTestUtils.m @@ -0,0 +1,98 @@ +// Copyright 2016 LinkedIn Corporation +// Licensed under the BSD 2-Clause License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://opensource.org/licenses/BSD-2-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +#import "BPXCTestUtils.h" + +#import +#import "XCTestSuite.h" +#import "XCTestCase.h" +#import "BPLoggingUtils.h" +#import "BPTestCaseInfo+Internal.h" + +@implementation BPXCTestUtils + ++ (void)logAllTestsInBundleWithPath:(NSString *)bundlePath toFile:(NSString *)outputPath { + NSArray *testCases = [self enumerateTestCasesInBundleWithPath:bundlePath]; + + // Encode the test data + NSError *encodingError; + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:testCases requiringSecureCoding:YES error:&encodingError]; + + // Write to file. + NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:outputPath]; + [fileHandle writeData:data]; + [fileHandle closeFile]; + + [BPLoggingUtils log:[NSString stringWithFormat:@"Wrote to file: %@.", outputPath]]; +} + ++ (NSArray *)enumerateTestCasesInBundleWithPath:(NSString *)bundlePath { + NSBundle *bundle = [NSBundle bundleWithPath:bundlePath]; + if (!bundle || !bundle.executablePath) { + [BPLoggingUtils logError:[NSString stringWithFormat:@"Unable to get executable path from bundle: %@", bundle]]; + return @[]; + } + return [self enumerateTestCasesInBundle:bundle]; +} + ++ (NSArray *)enumerateTestCasesInBundle:(NSBundle *)bundle { + /** + We need to remove the XCTest preference before continuing so that + we can open the bundle without actually executing it. If you start + to see logs resembling test output, it means this hasn't been done. + + TLDR: opening an .xctest file will normally cause the linker to load + the XCTest framework, which will trigger `XCTestSuite.initialize` + and start running the tests. + */ + + [NSUserDefaults.standardUserDefaults removeObjectForKey:@"XCTest"]; + [NSUserDefaults.standardUserDefaults synchronize]; + + /** + We must actually open the test bundle so that all of the test cases are loaded into memory. + We use `dlopen` here instead of `NSBundle.loadAndReturnError` for more informative messages on error. + */ + if (dlopen(bundle.executablePath.UTF8String, RTLD_LAZY) == NULL) { + [BPLoggingUtils logError:[NSString stringWithFormat:@"Unable to open test bundle's executable path - %@", bundle.executablePath]]; + + [BPLoggingUtils logError:@"What's the error???"]; + fprintf(stderr, "%s\n", dlerror()); + return @[]; + } + + [NSUserDefaults.standardUserDefaults setObject:@"None" forKey:@"XCTest"]; + + /** + Note that `XCTestSuite`, `XCTestCase`, etc all subclass `XCTest`, so to enumerate all tests in the current + bundle, we'll want to start `XCTestSuite.allTests`, and expand out all nested `XCTestSuite`s, adding + the suite's `.tests` to our array. + */ + NSMutableArray *testList = [NSMutableArray array]; + NSMutableArray *queue = [@[XCTestSuite.allTests] mutableCopy]; + while (queue.count) { + XCTest *test = queue.firstObject; + [queue removeObjectAtIndex:0]; + // If it's another nested XCTestSuite, keep going deeper! + // If it's an XCTestCase, we've hit a leaf and can just add it to our `testList`. + if ([test isKindOfClass:XCTestSuite.class]) { + XCTestSuite *testSuite = (XCTestSuite *)test; + [queue addObjectsFromArray:testSuite.tests]; + } else if ([test isKindOfClass:XCTestCase.class]) { + XCTestCase *testCase = (XCTestCase *)test; + [BPLoggingUtils log:[NSString stringWithFormat:@"testCase: %@", testCase]]; + [testList addObject:[BPTestCaseInfo infoFromTestCase:testCase]]; + } else { + [BPLoggingUtils logError:@"Found a currently unhandled XCTest type while enumerating tests"]; + } + } + return testList; +} + +@end diff --git a/BPTestInspector/Internal/PrivateHeaders/CDStructures.h b/BPTestInspector/Internal/PrivateHeaders/CDStructures.h new file mode 100644 index 00000000..55bc3957 --- /dev/null +++ b/BPTestInspector/Internal/PrivateHeaders/CDStructures.h @@ -0,0 +1,57 @@ +// +// Generated by class-dump 3.5 (64 bit). +// +// class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. +// + +#pragma mark Blocks + +typedef void (^CDUnknownBlockType)(void); // return type and parameters are unknown + +#pragma mark Named Structures + +struct DTXMachMessage { + struct { + struct { + unsigned int _field1; + unsigned int _field2; + unsigned int _field3; + unsigned int _field4; + unsigned int _field5; + int _field6; + } _field1; + unsigned int _field2; + } _field1; + char _field2[32672]; + char _field3[68]; +}; + +//struct DTXMessageHeader { +// unsigned int _field1; +// unsigned int _field2; +// unsigned short _field3; +// unsigned short _field4; +// unsigned int _field5; +// struct DTXMessageRoutingInfo _field6; +//}; + +struct DTXMessageRoutingInfo { + unsigned int _field1; + unsigned int _field2; + unsigned int _field3; + unsigned int :1; + unsigned int :31; +}; + +struct __va_list_tag { + unsigned int _field1; + unsigned int _field2; + void *_field3; + void *_field4; +}; + +//struct mach_timebase_info { +// unsigned int numer; +// unsigned int denom; +//}; + diff --git a/BPTestInspector/Internal/PrivateHeaders/Protocols/NSObject-Protocol.h b/BPTestInspector/Internal/PrivateHeaders/Protocols/NSObject-Protocol.h new file mode 100644 index 00000000..3320b3c9 --- /dev/null +++ b/BPTestInspector/Internal/PrivateHeaders/Protocols/NSObject-Protocol.h @@ -0,0 +1,33 @@ +// +// Generated by class-dump 3.5 (64 bit). +// +// class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. +// + +@class NSString, Protocol; + +@protocol NSObject +@property(readonly, copy) NSString *description; +@property(readonly) Class superclass; +@property(readonly) unsigned long long hash; +- (struct _NSZone *)zone; +- (unsigned long long)retainCount; +- (id)autorelease; +- (oneway void)release; +- (id)retain; +- (BOOL)respondsToSelector:(SEL)arg1; +- (BOOL)conformsToProtocol:(Protocol *)arg1; +- (BOOL)isMemberOfClass:(Class)arg1; +- (BOOL)isKindOfClass:(Class)arg1; +- (BOOL)isProxy; +- (id)performSelector:(SEL)arg1 withObject:(id)arg2 withObject:(id)arg3; +- (id)performSelector:(SEL)arg1 withObject:(id)arg2; +- (id)performSelector:(SEL)arg1; +- (id)self; +- (Class)class; +- (BOOL)isEqual:(id)arg1; + +@optional +@property(readonly, copy) NSString *debugDescription; +@end + diff --git a/BPTestInspector/Internal/PrivateHeaders/Protocols/XCTActivity-Protocol.h b/BPTestInspector/Internal/PrivateHeaders/Protocols/XCTActivity-Protocol.h new file mode 100644 index 00000000..74becde5 --- /dev/null +++ b/BPTestInspector/Internal/PrivateHeaders/Protocols/XCTActivity-Protocol.h @@ -0,0 +1,15 @@ +// +// Generated by class-dump 3.5 (64 bit) (Debug version compiled May 11 2021 09:30:43). +// +// Copyright (C) 1997-2019 Steve Nygard. +// + +@import Foundation; + +@class NSString, XCTAttachment; + +@protocol XCTActivity +@property(readonly, copy) NSString *name; +- (void)addAttachment:(XCTAttachment *)arg1; +@end + diff --git a/BPTestInspector/Internal/PrivateHeaders/Protocols/XCTIssueHandling-Protocol.h b/BPTestInspector/Internal/PrivateHeaders/Protocols/XCTIssueHandling-Protocol.h new file mode 100644 index 00000000..4dc2a6e1 --- /dev/null +++ b/BPTestInspector/Internal/PrivateHeaders/Protocols/XCTIssueHandling-Protocol.h @@ -0,0 +1,16 @@ +// +// Generated by class-dump 3.5 (64 bit) (Debug version compiled May 11 2021 09:30:43). +// +// Copyright (C) 1997-2019 Steve Nygard. +// + +@import Foundation; + +@class XCTExpectedFailureContext, XCTIssue; + +@protocol XCTIssueHandling +- (void)expectFailureWithContext:(XCTExpectedFailureContext *)arg1 inBlock:(void (^)(void))arg2; +- (void)expectFailureWithContext:(XCTExpectedFailureContext *)arg1; +- (void)handleIssue:(XCTIssue *)arg1; +@end + diff --git a/BPTestInspector/Internal/PrivateHeaders/Protocols/XCTWaiterDelegate-Protocol.h b/BPTestInspector/Internal/PrivateHeaders/Protocols/XCTWaiterDelegate-Protocol.h new file mode 100644 index 00000000..ca59b949 --- /dev/null +++ b/BPTestInspector/Internal/PrivateHeaders/Protocols/XCTWaiterDelegate-Protocol.h @@ -0,0 +1,17 @@ +// +// Generated by class-dump 3.5 (64 bit) (Debug version compiled May 11 2021 09:30:43). +// +// Copyright (C) 1997-2019 Steve Nygard. +// + +@import Foundation; + +@class NSArray, XCTWaiter, XCTestExpectation; + +@protocol XCTWaiterDelegate +- (void)nestedWaiter:(XCTWaiter *)arg1 wasInterruptedByTimedOutWaiter:(XCTWaiter *)arg2; +- (void)waiter:(XCTWaiter *)arg1 didFulfillInvertedExpectation:(XCTestExpectation *)arg2; +- (void)waiter:(XCTWaiter *)arg1 fulfillmentDidViolateOrderingConstraintsForExpectation:(XCTestExpectation *)arg2 requiredExpectation:(XCTestExpectation *)arg3; +- (void)waiter:(XCTWaiter *)arg1 didTimeoutWithUnfulfilledExpectations:(NSArray *)arg2; +@end + diff --git a/BPTestInspector/Internal/PrivateHeaders/Protocols/XCTestObservation-Protocol.h b/BPTestInspector/Internal/PrivateHeaders/Protocols/XCTestObservation-Protocol.h new file mode 100644 index 00000000..46528735 --- /dev/null +++ b/BPTestInspector/Internal/PrivateHeaders/Protocols/XCTestObservation-Protocol.h @@ -0,0 +1,27 @@ +// +// Generated by class-dump 3.5 (64 bit) (Debug version compiled May 11 2021 09:30:43). +// +// Copyright (C) 1997-2019 Steve Nygard. +// + +//#import "NSObject-Protocol.h" + +@class NSBundle, NSString, XCTExpectedFailure, XCTIssue, XCTestCase, XCTestSuite; + +@protocol XCTestObservation + +@optional +- (void)testCase:(XCTestCase *)arg1 didFailWithDescription:(NSString *)arg2 inFile:(NSString *)arg3 atLine:(unsigned long long)arg4; +- (void)testSuite:(XCTestSuite *)arg1 didFailWithDescription:(NSString *)arg2 inFile:(NSString *)arg3 atLine:(unsigned long long)arg4; +- (void)testCaseDidFinish:(XCTestCase *)arg1; +- (void)testCase:(XCTestCase *)arg1 didRecordExpectedFailure:(XCTExpectedFailure *)arg2; +- (void)testCase:(XCTestCase *)arg1 didRecordIssue:(XCTIssue *)arg2; +- (void)testCaseWillStart:(XCTestCase *)arg1; +- (void)testSuiteDidFinish:(XCTestSuite *)arg1; +- (void)testSuite:(XCTestSuite *)arg1 didRecordExpectedFailure:(XCTExpectedFailure *)arg2; +- (void)testSuite:(XCTestSuite *)arg1 didRecordIssue:(XCTIssue *)arg2; +- (void)testSuiteWillStart:(XCTestSuite *)arg1; +- (void)testBundleDidFinish:(NSBundle *)arg1; +- (void)testBundleWillStart:(NSBundle *)arg1; +@end + diff --git a/BPTestInspector/Internal/PrivateHeaders/Protocols/_XCTestObservationInternal-Protocol.h b/BPTestInspector/Internal/PrivateHeaders/Protocols/_XCTestObservationInternal-Protocol.h new file mode 100644 index 00000000..0aee3569 --- /dev/null +++ b/BPTestInspector/Internal/PrivateHeaders/Protocols/_XCTestObservationInternal-Protocol.h @@ -0,0 +1,17 @@ +// +// Generated by class-dump 3.5 (64 bit) (Debug version compiled May 11 2021 09:30:43). +// +// Copyright (C) 1997-2019 Steve Nygard. +// + +#import "_XCTestObservationPrivate-Protocol.h" + +@class NSArray, NSNumber, NSString, XCTSourceCodeContext, XCTestCase, XCTestRun; + +@protocol _XCTestObservationInternal <_XCTestObservationPrivate> + +@optional +- (void)_testCase:(XCTestRun *)arg1 didMeasureValues:(NSArray *)arg2 forPerformanceMetricID:(NSString *)arg3 name:(NSString *)arg4 unitsOfMeasurement:(NSString *)arg5 baselineName:(NSString *)arg6 baselineAverage:(NSNumber *)arg7 maxPercentRegression:(NSNumber *)arg8 maxPercentRelativeStandardDeviation:(NSNumber *)arg9 maxRegression:(NSNumber *)arg10 maxStandardDeviation:(NSNumber *)arg11 file:(NSString *)arg12 line:(unsigned long long)arg13 polarity:(long long)arg14; +- (void)testCase:(XCTestCase *)arg1 wasSkippedWithDescription:(NSString *)arg2 sourceCodeContext:(XCTSourceCodeContext *)arg3; +@end + diff --git a/BPTestInspector/Internal/PrivateHeaders/Protocols/_XCTestObservationPrivate-Protocol.h b/BPTestInspector/Internal/PrivateHeaders/Protocols/_XCTestObservationPrivate-Protocol.h new file mode 100644 index 00000000..3dfc04d4 --- /dev/null +++ b/BPTestInspector/Internal/PrivateHeaders/Protocols/_XCTestObservationPrivate-Protocol.h @@ -0,0 +1,17 @@ +// +// Generated by class-dump 3.5 (64 bit) (Debug version compiled May 11 2021 09:30:43). +// +// Copyright (C) 1997-2019 Steve Nygard. +// + +#import "XCTestObservation-Protocol.h" + +@class XCActivityRecord, XCTContext; + +@protocol _XCTestObservationPrivate + +@optional +- (void)_context:(XCTContext *)arg1 didFinishActivity:(XCActivityRecord *)arg2; +- (void)_context:(XCTContext *)arg1 willStartActivity:(XCActivityRecord *)arg2; +@end + diff --git a/BPTestInspector/Internal/PrivateHeaders/XCTest.h b/BPTestInspector/Internal/PrivateHeaders/XCTest.h new file mode 100644 index 00000000..155a225b --- /dev/null +++ b/BPTestInspector/Internal/PrivateHeaders/XCTest.h @@ -0,0 +1,55 @@ +// +// Generated by class-dump 3.5 (64 bit) (Debug version compiled May 11 2021 09:30:43). +// +// Copyright (C) 1997-2019 Steve Nygard. +// + +@import Foundation; + +#import "XCTIssueHandling-Protocol.h" + +@class NSString, XCTExpectedFailureContextManager, XCTTestIdentifier, XCTestObservationCenter, XCTestRun; + +@interface XCTest : NSObject +{ + XCTExpectedFailureContextManager *_expectedFailureContextManager; + XCTestRun *_testRun; + XCTestObservationCenter *_observationCenter; +} + +- (void)expectFailureWithContext:(id)arg1 inBlock:(id)arg2; +- (void)expectFailureWithContext:(id)arg1; +- (void)_checkForExpectedFailureMatchingIssue:(id)arg1; +- (id)expectedFailureContextManager; +- (void)handleIssue:(id)arg1; +- (long long)defaultExecutionOrderCompare:(id)arg1; +@property(readonly) NSString *nameForLegacyLogging; +@property(readonly) NSString *languageAgnosticTestMethodName; +@property(readonly) NSString *languageAgnosticTestClassName; +- (_Bool)tearDownWithError:(id *)arg1; +- (void)tearDown; +- (void)setUp; +- (_Bool)setUpWithError:(id *)arg1; +- (_Bool)_shouldRerunTest; +- (void)runTest; +- (void)performTest:(id)arg1; +@property(retain) XCTestObservationCenter *observationCenter; // @synthesize observationCenter=_observationCenter; +@property(readonly) XCTestRun *testRun; // @synthesize testRun=_testRun; +@property(readonly) Class testRunClass; +@property(readonly) Class _requiredTestRunBaseClass; +@property(readonly, copy) NSString *name; +@property(readonly) unsigned long long testCaseCount; +- (id)_duplicate; +@property(readonly) NSString *_methodNameForReporting; +@property(readonly) NSString *_classNameForReporting; +- (void)removeTestsWithIdentifierInSet:(id)arg1; + +// Remaining properties +@property(readonly, copy) NSString *debugDescription; +@property(readonly, copy) NSString *description; +@property(readonly) NSUInteger hash; +@property(readonly, getter=_identifier) XCTTestIdentifier *identifier; // @dynamic identifier; +@property(readonly) Class superclass; + +@end + diff --git a/BPTestInspector/Internal/PrivateHeaders/XCTestCase.h b/BPTestInspector/Internal/PrivateHeaders/XCTestCase.h new file mode 100644 index 00000000..89c788d2 --- /dev/null +++ b/BPTestInspector/Internal/PrivateHeaders/XCTestCase.h @@ -0,0 +1,189 @@ +// +// Generated by class-dump 3.5 (64 bit) (Debug version compiled May 11 2021 09:30:43). +// +// Copyright (C) 1997-2019 Steve Nygard. +// + +#import "XCTest.h" + +#import "XCTActivity-Protocol.h" +#import "XCTWaiterDelegate-Protocol.h" + +@class MXMInstrument, NSArray, NSDictionary, NSInvocation, NSMutableArray, NSMutableDictionary, NSMutableSet, NSObject, NSString, NSThread, XCTAttachmentManager, XCTIssue, XCTSkippedTestContext, XCTTestIdentifier, XCTWaiter, XCTestCaseRun; +@protocol OS_dispatch_source; + +@interface XCTestCase : XCTest +{ + _Bool _continueAfterFailure; + _Bool __preciseTimeoutsEnabled; + _Bool _isMeasuringMetrics; + _Bool __didMeasureMetrics; + _Bool __didStartMeasuring; + _Bool __didStopMeasuring; + _Bool _hasDequeuedTeardownBlocks; + _Bool _hasReportedFailuresToTestCaseRun; + _Bool _canHandleInterruptions; + _Bool _shouldHaltWhenReceivesControl; + _Bool _shouldSetShouldHaltWhenReceivesControl; + _Bool _hasAttemptedToCaptureScreenshotOnFailure; + XCTTestIdentifier *_identifier; + NSInvocation *_invocation; + double _executionTimeAllowance; + NSArray *_activePerformanceMetricIDs; + unsigned long long _startWallClockTime; + struct time_value _startUserTime; + struct time_value _startSystemTime; + unsigned long long _measuringIteration; + MXMInstrument *_instrument; + long long _runLoopNestingCount; + NSMutableArray *_teardownBlocks; + NSMutableArray *_primaryThreadBlocks; + XCTAttachmentManager *_attachmentManager; + NSDictionary *_activityAggregateStatistics; + NSObject *_timeoutSource; + unsigned long long _signpostID; + NSThread *_primaryThread; + NSMutableSet *_previousIssuesAssociatedWithSwiftErrors; + NSMutableArray *_enqueuedIssues; + NSMutableArray *_expectations; + XCTWaiter *_currentWaiter; + XCTSkippedTestContext *_skippedTestContext; + XCTestCaseRun *_testCaseRun; + NSMutableDictionary *__perfMetricsForID; +} + ++ (id)_baselineDictionary; ++ (_Bool)_treatMissingBaselinesAsTestFailures; ++ (id)defaultMeasureOptions; ++ (id)defaultMetrics; ++ (id)defaultPerformanceMetrics; ++ (_Bool)_reportPerformanceFailuresForLargeImprovements; ++ (id)testInvocations; ++ (_Bool)isInheritingTestCases; ++ (id)bundle; ++ (id)testCaseWithSelector:(SEL)arg1; ++ (_Bool)_isDiscoverable; ++ (id)testCaseWithInvocation:(id)arg1; ++ (void)tearDown; ++ (void)setUp; ++ (id)defaultTestSuite; ++ (id)allTestMethodInvocations; ++ (void)_allTestMethodInvocations:(id)arg1; ++ (id)testMethodInvocations; ++ (id)allSubclassesOutsideXCTest; ++ (id)allSubclasses; ++ (id)_allSubclasses; + +@property(retain) NSMutableDictionary *_perfMetricsForID; // @synthesize _perfMetricsForID=__perfMetricsForID; +@property(retain) XCTestCaseRun *testCaseRun; // @synthesize testCaseRun=_testCaseRun; +@property(nonatomic) _Bool shouldSetShouldHaltWhenReceivesControl; // @synthesize shouldSetShouldHaltWhenReceivesControl=_shouldSetShouldHaltWhenReceivesControl; +@property(nonatomic) _Bool shouldHaltWhenReceivesControl; // @synthesize shouldHaltWhenReceivesControl=_shouldHaltWhenReceivesControl; +@property(retain) NSThread *primaryThread; // @synthesize primaryThread=_primaryThread; +@property(copy) NSDictionary *activityAggregateStatistics; // @synthesize activityAggregateStatistics=_activityAggregateStatistics; +@property long long runLoopNestingCount; // @synthesize runLoopNestingCount=_runLoopNestingCount; +@property _Bool _didStopMeasuring; // @synthesize _didStopMeasuring=__didStopMeasuring; +@property _Bool _didStartMeasuring; // @synthesize _didStartMeasuring=__didStartMeasuring; +@property _Bool _didMeasureMetrics; // @synthesize _didMeasureMetrics=__didMeasureMetrics; +@property(nonatomic) _Bool _preciseTimeoutsEnabled; // @synthesize _preciseTimeoutsEnabled=__preciseTimeoutsEnabled; +@property(readonly) double _effectiveExecutionTimeAllowance; +- (void)_resetTimer; +- (void)_stopTimeoutTimer; +- (void)_startTimeoutTimer; +- (void)_exceededExecutionTimeAllowance; +@property unsigned long long maxDurationInMinutes; +@property double executionTimeAllowance; // @synthesize executionTimeAllowance=_executionTimeAllowance; +- (void)addAttachment:(id)arg1; +- (void)runActivityNamed:(id)arg1 inScope:(id)arg2; +- (void)startActivityWithTitle:(id)arg1 block:(id)arg2; +- (void)startActivityWithTitle:(id)arg1 type:(id)arg2 block:(id)arg3; +- (void)measureMetrics:(id)arg1 automaticallyStartMeasuring:(_Bool)arg2 forBlock:(id)arg3; +- (void)registerDefaultMetrics; +- (id)baselinesDictionaryForTest; +- (void)_logAndReportPerformanceMetrics:(id)arg1 perfMetricResultsForIDs:(id)arg2 withBaselinesForTest:(id)arg3; +- (void)_logAndReportPerformanceMetrics:(id)arg1 perfMetricResultsForIDs:(id)arg2 withBaselinesForTest:(id)arg3 defaultBaselinesForPerfMetricID:(id)arg4; +- (void)registerMetricID:(id)arg1 name:(id)arg2 unitString:(id)arg3 polarity:(long long)arg4; +- (void)registerMetricID:(id)arg1 name:(id)arg2 unitString:(id)arg3; +- (void)registerMetricID:(id)arg1 name:(id)arg2 unit:(id)arg3; +- (void)reportMetric:(id)arg1 reportFailures:(_Bool)arg2; +- (void)reportMeasurements:(id)arg1 forMetricID:(id)arg2 reportFailures:(_Bool)arg3; +- (void)_recordValues:(id)arg1 forPerformanceMetricID:(id)arg2 name:(id)arg3 unitsOfMeasurement:(id)arg4 baselineName:(id)arg5 baselineAverage:(id)arg6 maxPercentRegression:(id)arg7 maxPercentRelativeStandardDeviation:(id)arg8 maxRegression:(id)arg9 maxStandardDeviation:(id)arg10 file:(id)arg11 line:(unsigned long long)arg12 polarity:(long long)arg13; +- (void)measureWithMetrics:(id)arg1 options:(id)arg2 block:(id)arg3; +- (void)measureWithMetrics:(id)arg1 block:(id)arg2; +- (void)measureWithOptions:(id)arg1 block:(id)arg2; +- (void)measureBlock:(id)arg1; +- (void)stopMeasuring; +- (void)startMeasuring; +@property(readonly) id minimumOperatingSystemVersion; +- (void)_logMemoryGraphDataFromFilePath:(id)arg1 withTitle:(id)arg2; +- (void)_logMemoryGraphData:(id)arg1 withTitle:(id)arg2; +- (unsigned long long)numberOfTestIterationsForTestWithSelector:(SEL)arg1; +- (id)addAdditionalIterationsBasedOnOptions:(id)arg1; +- (void)afterTestIteration:(unsigned long long)arg1 selector:(SEL)arg2; +- (void)beforeTestIteration:(unsigned long long)arg1 selector:(SEL)arg2; +- (void)tearDownTestWithSelector:(SEL)arg1; +- (void)setUpTestWithSelector:(SEL)arg1; +- (void)_addTeardownBlock:(id)arg1; +- (void)addTeardownBlock:(id)arg1; +- (void)_purgeTeardownBlocks; +- (void)performTest:(id)arg1; +- (_Bool)_shouldRerunTest; +- (void)_reportFailuresAtFile:(id)arg1 line:(unsigned long long)arg2 forTestAssertionsInScope:(id)arg3; +- (void)invokeTest; +- (Class)testRunClass; +- (Class)_requiredTestRunBaseClass; +- (void)recordIssue:(id)arg1; +@property _Bool continueAfterFailure; // @synthesize continueAfterFailure=_continueAfterFailure; +@property(retain) NSInvocation *invocation; // @synthesize invocation=_invocation; +- (void)dealloc; +@property(readonly, copy) NSString *description; +- (_Bool)isEqual:(id)arg1; +- (long long)defaultExecutionOrderCompare:(id)arg1; +- (id)nameForLegacyLogging; +@property(readonly, copy) NSString *name; +- (id)languageAgnosticTestMethodName; +- (id)_identifier; +- (unsigned long long)testCaseCount; +- (id)bundle; +- (id)initWithSelector:(SEL)arg1; +- (id)_duplicate; +- (id)initWithInvocation:(id)arg1; +- (id)init; +- (void)removeUIInterruptionMonitor:(id)arg1; +- (id)addUIInterruptionMonitorWithDescription:(id)arg1 handler:(id)arg2; +- (void)nestedWaiter:(id)arg1 wasInterruptedByTimedOutWaiter:(id)arg2; +- (void)waiter:(id)arg1 didFulfillInvertedExpectation:(id)arg2; +- (void)waiter:(id)arg1 fulfillmentDidViolateOrderingConstraintsForExpectation:(id)arg2 requiredExpectation:(id)arg3; +- (void)waiter:(id)arg1 didTimeoutWithUnfulfilledExpectations:(id)arg2; +- (id)expectationForPredicate:(id)arg1 evaluatedWithObject:(id)arg2 handler:(id)arg3; +- (id)expectationForNotification:(id)arg1 object:(id)arg2 notificationCenter:(id)arg3 handler:(id)arg4; +- (id)expectationForNotification:(id)arg1 object:(id)arg2 handler:(id)arg3; +- (id)keyValueObservingExpectationForObject:(id)arg1 keyPath:(id)arg2 handler:(id)arg3; +- (id)keyValueObservingExpectationForObject:(id)arg1 keyPath:(id)arg2 expectedValue:(id)arg3; +- (id)expectationWithDescription:(id)arg1; +- (void)waitForExpectations:(id)arg1 timeout:(double)arg2 enforceOrder:(_Bool)arg3; +- (void)waitForExpectations:(id)arg1 timeout:(double)arg2; +- (void)waitForExpectationsWithTimeout:(double)arg1 handler:(id)arg2; +- (id)_expectationForDistributedNotification:(id)arg1 object:(id)arg2 handler:(id)arg3; +- (id)_expectationForDarwinNotification:(id)arg1; +- (void)recordFailureWithDescription:(id)arg1 inFile:(id)arg2 atLine:(unsigned long long)arg3 expected:(_Bool)arg4; +- (void)_interruptOrMarkForLaterInterruption; +- (_Bool)_caughtUnhandledDeveloperExceptionPermittingControlFlowInterruptions:(_Bool)arg1 caughtInterruptionException:(_Bool *)arg2 whileExecutingBlock:(id)arg3; +- (_Bool)_caughtUnhandledDeveloperExceptionPermittingControlFlowInterruptions:(_Bool)arg1 whileExecutingBlock:(id)arg2; +- (id)_issueWithFailureScreenshotAttachedToIssue:(id)arg1; +- (void)_handleIssue:(id)arg1; +- (void)_dequeueIssues; +- (void)_enqueueIssue:(id)arg1; +- (void)_recordIssue:(id)arg1; +- (_Bool)_isDuplicateOfIssueAssociatedWithSameSwiftError:(id)arg1; +- (void)expectFailureWithContext:(id)arg1; +@property(copy) XCTIssue *candidateIssueForCurrentThread; +- (id)_storageKeyForCandidateIssue; +- (void)handleIssue:(id)arg1; + +// Remaining properties +@property(readonly, copy) NSString *debugDescription; +@property(readonly) NSUInteger hash; +@property(readonly) Class superclass; + +@end + diff --git a/BPTestInspector/Internal/PrivateHeaders/XCTestLog.h b/BPTestInspector/Internal/PrivateHeaders/XCTestLog.h new file mode 100644 index 00000000..f9ebe091 --- /dev/null +++ b/BPTestInspector/Internal/PrivateHeaders/XCTestLog.h @@ -0,0 +1,59 @@ +// +// Generated by class-dump 3.5 (64 bit) (Debug version compiled May 11 2021 09:30:43). +// +// Copyright (C) 1997-2019 Steve Nygard. +// + +#import "XCTestObserver.h" + +#import "_XCTestObservationInternal-Protocol.h" +#import "CDStructures.h" + +@class NSFileHandle, NSString, XCTestSuite, XCTestCase; + +@interface XCTestLog : XCTestObserver <_XCTestObservationInternal> +{ +} + +// Currently Handled + +- (void)testSuiteWillStart:(XCTestSuite *)suite; +- (void)testSuiteDidFinish:(XCTestCase *)testCase; +- (void)testCaseWillStart:(XCTestCase *)testCase; +- (void)testCaseDidFinish:(XCTestCase *)testCase; +- (void)testCase:(XCTestCase *)testCase +didFailWithDescription:(NSString *)description + inFile:(NSString *)file + atLine:(NSUInteger)line; +- (void)testCase:(XCTestCase *)testCase +wasSkippedWithDescription:(NSString *)description + inFile:(NSString *)file + atLine:(NSUInteger)line; + +// TODO: @lthrockm - add support + +- (void)testSuite:(id)arg1 didRecordExpectedFailure:(id)arg2; +- (void)testSuite:(id)arg1 didFailWithDescription:(id)arg2 inFile:(id)arg3 atLine:(unsigned long long)arg4; + +// Other + ++ (id)_messageForTest:(id)arg1 didMeasureValues:(id)arg2 forPerformanceMetricID:(id)arg3 name:(id)arg4 unitsOfMeasurement:(id)arg5 baselineName:(id)arg6 baselineAverage:(id)arg7 maxPercentRegression:(id)arg8 maxPercentRelativeStandardDeviation:(id)arg9 maxRegression:(id)arg10 maxStandardDeviation:(id)arg11 file:(id)arg12 line:(unsigned long long)arg13 polarity:(long long)arg14; +- (void)_context:(id)arg1 willStartActivity:(id)arg2; +- (void)testCase:(id)arg1 didRecordExpectedFailure:(id)arg2; +- (void)_testCase:(id)arg1 didMeasureValues:(id)arg2 forPerformanceMetricID:(id)arg3 name:(id)arg4 unitsOfMeasurement:(id)arg5 baselineName:(id)arg6 baselineAverage:(id)arg7 maxPercentRegression:(id)arg8 maxPercentRelativeStandardDeviation:(id)arg9 maxRegression:(id)arg10 maxStandardDeviation:(id)arg11 file:(id)arg12 line:(unsigned long long)arg13 polarity:(long long)arg14; +- (void)_testWithName:(id)arg1 didRecordExpectedFailure:(id)arg2; +- (void)_testDidFail:(id)arg1 withDescription:(id)arg2 inFile:(id)arg3 atLine:(unsigned long long)arg4; +- (void)logTestMessage:(id)arg1; +- (void)testLogWithFormat:(id)arg1 arguments:(struct __va_list_tag [1])arg2; +- (void)testLogWithFormat:(id)arg1; +@property(readonly) NSFileHandle *logFileHandle; +- (id)dateFormatter; + +// Remaining properties +@property(readonly, copy) NSString *debugDescription; +@property(readonly, copy) NSString *description; +@property(readonly) NSUInteger hash; +@property(readonly) Class superclass; + +@end + diff --git a/BPTestInspector/Internal/PrivateHeaders/XCTestObserver.h b/BPTestInspector/Internal/PrivateHeaders/XCTestObserver.h new file mode 100644 index 00000000..3e09a3c3 --- /dev/null +++ b/BPTestInspector/Internal/PrivateHeaders/XCTestObserver.h @@ -0,0 +1,23 @@ +// +// Generated by class-dump 3.5 (64 bit) (Debug version compiled May 11 2021 09:30:43). +// +// Copyright (C) 1997-2019 Steve Nygard. +// + +#import + +@interface XCTestObserver : NSObject +{ +} + +- (void)testCaseDidFail:(id)arg1 withDescription:(id)arg2 inFile:(id)arg3 atLine:(unsigned long long)arg4; +- (void)testCaseDidStop:(id)arg1; +- (void)testCaseDidStart:(id)arg1; +- (void)testSuiteDidFail:(id)arg1 withDescription:(id)arg2 inFile:(id)arg3 atLine:(unsigned long long)arg4; +- (void)testSuiteDidStop:(id)arg1; +- (void)testSuiteDidStart:(id)arg1; +- (void)stopObserving; +- (void)startObserving; + +@end + diff --git a/BPTestInspector/Internal/PrivateHeaders/XCTestRun.h b/BPTestInspector/Internal/PrivateHeaders/XCTestRun.h new file mode 100644 index 00000000..223c7db6 --- /dev/null +++ b/BPTestInspector/Internal/PrivateHeaders/XCTestRun.h @@ -0,0 +1,73 @@ +// +// Generated by class-dump 3.5 (64 bit) (Debug version compiled May 11 2021 09:30:43). +// +// Copyright (C) 1997-2019 Steve Nygard. +// + +#import + +@class NSDate, XCTIssue, XCTest, XCTestObservationCenter; + +@interface XCTestRun : NSObject +{ + _Bool _hasBeenSkipped; + _Bool _hasStarted; + _Bool _hasStopped; + _Bool _hasExpectedFailure; + XCTest *_test; + unsigned long long _executionCount; + unsigned long long _failureCount; + unsigned long long _unexpectedExceptionCount; + double _startTimeInterval; + double _stopTimeInterval; + XCTIssue *_candidateIssue; + unsigned long long __runCount; + unsigned long long _executionCountBeforeCrash; + unsigned long long _skipCountBeforeCrash; + unsigned long long _expectedFailureCountBeforeCrash; + unsigned long long _failureCountBeforeCrash; + unsigned long long _unexpectedExceptionCountBeforeCrash; +} + ++ (id)testRunWithTest:(id)arg1; +//- (void).cxx_destruct; +@property unsigned long long unexpectedExceptionCountBeforeCrash; // @synthesize unexpectedExceptionCountBeforeCrash=_unexpectedExceptionCountBeforeCrash; +@property unsigned long long failureCountBeforeCrash; // @synthesize failureCountBeforeCrash=_failureCountBeforeCrash; +@property unsigned long long expectedFailureCountBeforeCrash; // @synthesize expectedFailureCountBeforeCrash=_expectedFailureCountBeforeCrash; +@property unsigned long long skipCountBeforeCrash; // @synthesize skipCountBeforeCrash=_skipCountBeforeCrash; +@property unsigned long long executionCountBeforeCrash; // @synthesize executionCountBeforeCrash=_executionCountBeforeCrash; +@property unsigned long long _runCount; // @synthesize _runCount=__runCount; +@property(retain) XCTIssue *candidateIssue; // @synthesize candidateIssue=_candidateIssue; +@property _Bool hasExpectedFailure; // @synthesize hasExpectedFailure=_hasExpectedFailure; +@property _Bool hasStopped; // @synthesize hasStopped=_hasStopped; +@property _Bool hasStarted; // @synthesize hasStarted=_hasStarted; +@property double stopTimeInterval; // @synthesize stopTimeInterval=_stopTimeInterval; +@property double startTimeInterval; // @synthesize startTimeInterval=_startTimeInterval; +@property _Bool hasBeenSkipped; // @synthesize hasBeenSkipped=_hasBeenSkipped; +@property unsigned long long unexpectedExceptionCount; // @synthesize unexpectedExceptionCount=_unexpectedExceptionCount; +@property unsigned long long failureCount; // @synthesize failureCount=_failureCount; +@property unsigned long long executionCount; // @synthesize executionCount=_executionCount; +@property(readonly) XCTest *test; // @synthesize test=_test; +- (void)_handleIssue:(id)arg1; +- (void)_recordIssue:(id)arg1; +- (void)recordIssue:(id)arg1; +- (void)recordExpectedFailure:(id)arg1; +- (void)recordSkipWithDescription:(id)arg1 sourceCodeContext:(id)arg2; +- (unsigned long long)expectedFailureCount; +@property(readonly) unsigned long long skipCount; +@property(readonly) _Bool hasSucceeded; +@property(readonly) unsigned long long testCaseCount; +@property(readonly) unsigned long long totalFailureCount; +- (void)stop; +- (void)start; +@property(readonly, copy) NSDate *stopDate; +@property(readonly, copy) NSDate *startDate; +@property(readonly) double testDuration; +@property(readonly) double totalDuration; +@property(readonly) XCTestObservationCenter *observationCenter; +- (id)description; +- (id)initWithTest:(id)arg1; +- (void)recordFailureWithDescription:(id)arg1 inFile:(id)arg2 atLine:(unsigned long long)arg3 expected:(_Bool)arg4; + +@end + diff --git a/BPTestInspector/Internal/PrivateHeaders/XCTestSuite.h b/BPTestInspector/Internal/PrivateHeaders/XCTestSuite.h new file mode 100644 index 00000000..2cb75449 --- /dev/null +++ b/BPTestInspector/Internal/PrivateHeaders/XCTestSuite.h @@ -0,0 +1,70 @@ +// +// Generated by class-dump 3.5 (64 bit) (Debug version compiled May 11 2021 09:30:43). +// +// Copyright (C) 1997-2019 Steve Nygard. +// + +#import "XCTest.h" + +typedef void (^CDUnknownBlockType)(void); +typedef void (*CDUnknownFunctionPointerType)(void); + +@class NSArray, NSDictionary, NSMutableArray, NSMutableDictionary, NSString, XCTTestIdentifier, XCTestConfiguration; + +@interface XCTestSuite : XCTest +{ + XCTTestIdentifier *_identifier; + NSString *_name; + NSMutableArray *_mutableTests; + XCTestConfiguration *_testConfiguration; + NSMutableDictionary *_mutableActivityAggregateStatistics; +} + ++ (NSArray *)allTests; + ++ (id)testClassSuitesForTestIdentifiers:(id)arg1 skippingTestIdentifiers:(id)arg2 randomNumberGenerator:(CDUnknownBlockType)arg3; ++ (id)testSuiteForTestConfiguration:(id)arg1; ++ (id)defaultTestSuite; ++ (id)testSuiteForTestCaseClass:(Class)arg1; ++ (id)testSuiteForTestCaseWithName:(id)arg1; ++ (id)testSuiteForBundlePath:(id)arg1; ++ (id)suiteForBundleCache; ++ (void)invalidateCache; ++ (id)_suiteForBundleCache; ++ (id)emptyTestSuiteNamedFromPath:(id)arg1; ++ (id)testSuiteWithName:(id)arg1; + +@property(readonly, copy) NSArray *tests; + +//- (void).cxx_destruct; +@property(readonly) NSMutableDictionary *mutableActivityAggregateStatistics; // @synthesize mutableActivityAggregateStatistics=_mutableActivityAggregateStatistics; +@property(retain) XCTestConfiguration *testConfiguration; // @synthesize testConfiguration=_testConfiguration; +@property(retain) NSMutableArray *mutableTests; // @synthesize mutableTests=_mutableTests; +@property(copy) NSString *name; // @synthesize name=_name; +- (id)_identifier; +- (id)_initWithTestConfiguration:(id)arg1; +- (void)_applyRandomExecutionOrderingWithGenerator:(CDUnknownBlockType)arg1; +- (void)_sortTestsUsingDefaultExecutionOrdering; +- (long long)defaultExecutionOrderCompare:(id)arg1; +@property(readonly) NSDictionary *activityAggregateStatistics; +- (void)_mergeActivityStatistics:(id)arg1; +- (void)runTestBasedOnRerunPolicy:(id)arg1 testRun:(id)arg2; +- (void)performTest:(id)arg1; +- (void)_performProtectedSectionForTest:(id)arg1 testSection:(CDUnknownBlockType)arg2; +- (void)_recordUnexpectedFailureForTestRun:(id)arg1 description:(id)arg2 exception:(id)arg3; +- (void)handleIssue:(id)arg1; +- (void)recordFailureWithDescription:(id)arg1 inFile:(id)arg2 atLine:(unsigned long long)arg3 expected:(_Bool)arg4; +- (Class)testRunClass; +- (Class)_requiredTestRunBaseClass; +- (_Bool)isEqual:(id)arg1; +- (unsigned long long)testCaseCount; +- (void)setTests:(id)arg1; +- (void)addTest:(id)arg1; +- (id)_testSuiteWithIdentifier:(id)arg1; +- (id)description; +- (id)initWithName:(id)arg1; +- (id)init; +- (void)removeTestsWithIdentifierInSet:(id)arg1; + +@end + diff --git a/BPTestInspector/Internal/PrivateHeaders/XCTestSuiteRun.h b/BPTestInspector/Internal/PrivateHeaders/XCTestSuiteRun.h new file mode 100644 index 00000000..56c3c18b --- /dev/null +++ b/BPTestInspector/Internal/PrivateHeaders/XCTestSuiteRun.h @@ -0,0 +1,33 @@ +// +// Generated by class-dump 3.5 (64 bit) (Debug version compiled May 11 2021 09:30:43). +// +// Copyright (C) 1997-2019 Steve Nygard. +// + +#import "XCTestRun.h" + +@class NSArray, NSMutableArray; + +@interface XCTestSuiteRun : XCTestRun +{ + NSMutableArray *_mutableTestRuns; +} + +//- (void).cxx_destruct; +@property(readonly) NSMutableArray *mutableTestRuns; // @synthesize mutableTestRuns=_mutableTestRuns; +- (void)recordExpectedFailure:(id)arg1; +- (void)_handleIssue:(id)arg1; +- (double)testDuration; +- (unsigned long long)unexpectedExceptionCount; +- (unsigned long long)failureCount; +- (unsigned long long)expectedFailureCount; +- (unsigned long long)skipCount; +- (unsigned long long)executionCount; +- (void)addTestRun:(id)arg1; +@property(readonly, copy) NSArray *testRuns; +- (void)stop; +- (void)start; +- (id)initWithTest:(id)arg1; + +@end + diff --git a/BPTestInspector/Internal/main.m b/BPTestInspector/Internal/main.m new file mode 100644 index 00000000..c9090fb2 --- /dev/null +++ b/BPTestInspector/Internal/main.m @@ -0,0 +1,55 @@ +// Copyright 2016 LinkedIn Corporation +// Licensed under the BSD 2-Clause License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://opensource.org/licenses/BSD-2-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +#import +#import +#import + +#import "BPXCTestUtils.h" +#import "BPLoggingUtils.h" +#import "BPTestInspectorConstants.h" + +/** + This wrapper around XCTest hijacks the process when two key env variables are set, hooking into the xctest process to + instead get information on what tests exist in the test suite. This allows Bluepill to do two main things: + + 1) It can create an aggregate timeout based on the number of tests to run + 2) It allows us to support opting-out of tests, rather than just opting in, as xctest provides no explicit opt-out api. + + When `BP_XCTEST_WRAPPER__LOGIC_TEST_BUNDLE` and `BP_XCTEST_WRAPPER__TEST_CASE_OUTPUT` are set, + the entire process will only output an encoded file with test case info at the path specified by `BP_XCTEST_WRAPPER__TEST_CASE_OUTPUT`, + and **no tests will actually be run**. + + When they are not set, tests will be run as normal with no other side effects. + */ +__attribute__((constructor)) +static void didLoad() { + [BPLoggingUtils log:@"Booting up the wrapper."]; + + #if !TARGET_OS_IOS + [BPLoggingUtils log:@"Returning."]; + return; + #endif + + // Grab relavent info from environment + NSString *bundlePath = NSProcessInfo.processInfo.environment[BPTestInspectorConstants.testBundleEnvironmentKey]; + NSString *outputPath = NSProcessInfo.processInfo.environment[BPTestInspectorConstants.outputPathEnvironmentKey]; + // Reset DYLD_INSERT_LIBRARIES and other env variables to avoid impacting future processes + unsetenv("DYLD_INSERT_LIBRARIES"); + unsetenv(BPTestInspectorConstants.testBundleEnvironmentKey.UTF8String); + unsetenv(BPTestInspectorConstants.outputPathEnvironmentKey.UTF8String); + + if (!bundlePath || !outputPath) { + return; + } + [BPLoggingUtils log:[NSString stringWithFormat:@"Will enumerate all testCases in bundle at %@", bundlePath]]; + [BPXCTestUtils logAllTestsInBundleWithPath:bundlePath toFile:outputPath]; + // Once tests are logged, we want the process to end. + exit(0); +} diff --git a/Bluepill.xcworkspace/contents.xcworkspacedata b/Bluepill.xcworkspace/contents.xcworkspacedata index f5ad27e0..a563c0e0 100644 --- a/Bluepill.xcworkspace/contents.xcworkspacedata +++ b/Bluepill.xcworkspace/contents.xcworkspacedata @@ -1,6 +1,9 @@ + + diff --git a/Bluepill.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/Bluepill.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index 54782e32..a6f6fb21 100644 --- a/Bluepill.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/Bluepill.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -4,5 +4,7 @@ IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded + PreviewsEnabled + diff --git a/Configs/BPMacTestInspector.xcconfig b/Configs/BPMacTestInspector.xcconfig new file mode 100644 index 00000000..371f806f --- /dev/null +++ b/Configs/BPMacTestInspector.xcconfig @@ -0,0 +1,8 @@ +CURRENT_PROJECT_VERSION = 1 +DYLIB_CURRENT_VERSION = 1 +DYLIB_COMPATIBILITY_VERSION = 1 +PRODUCT_NAME = BPMacTestInspector +EXECUTABLE_PREFIX = lib +SDKROOT = macosx +MACOSX_DEPLOYMENT_TARGET = 11.0 +COPY_PHASE_STRIP = NO diff --git a/Configs/BPTestInspector.xcconfig b/Configs/BPTestInspector.xcconfig new file mode 100644 index 00000000..964e63f0 --- /dev/null +++ b/Configs/BPTestInspector.xcconfig @@ -0,0 +1,9 @@ +CURRENT_PROJECT_VERSION = 1 +DYLIB_CURRENT_VERSION = 1 +DYLIB_COMPATIBILITY_VERSION = 1 +PRODUCT_NAME = BPTestInspector +EXECUTABLE_PREFIX = lib +SDKROOT = iphonesimulator +IPHONEOS_DEPLOYMENT_TARGET = 16.0 +COPY_PHASE_STRIP = NO +CODE_SIGN_IDENTITY = - // LTHROCKM - may need code signing for hosted tests diff --git a/bluepill/bluepill.xcodeproj/project.pbxproj b/bluepill/bluepill.xcodeproj/project.pbxproj index 03ef4457..794f1f4a 100644 --- a/bluepill/bluepill.xcodeproj/project.pbxproj +++ b/bluepill/bluepill.xcodeproj/project.pbxproj @@ -37,6 +37,11 @@ C4D6861A2267ABEF007D4237 /* bplib.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C4D686192267ABEF007D4237 /* bplib.framework */; }; C4FD8C581DB6E09B000ED28C /* BPPacker.m in Sources */ = {isa = PBXBuildFile; fileRef = C4FD8C571DB6E09B000ED28C /* BPPacker.m */; }; E49235FF22EA847700395D98 /* times.json in Resources */ = {isa = PBXBuildFile; fileRef = E49235FE22EA847700395D98 /* times.json */; }; + FB21F0C02A622FFE00682AC7 /* libBPMacTestInspector.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FB21F0BE2A622FFD00682AC7 /* libBPMacTestInspector.dylib */; }; + FB940A432B3032430071F2FA /* libBPMacTestInspector.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FB940A422B3032430071F2FA /* libBPMacTestInspector.dylib */; }; + FB940A442B3032430071F2FA /* libBPMacTestInspector.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = FB940A422B3032430071F2FA /* libBPMacTestInspector.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + FBA69FA82A45F889003BADBF /* BPLogicTestFixture_x86_64.xctest in Resources */ = {isa = PBXBuildFile; fileRef = FBA69FA62A45F889003BADBF /* BPLogicTestFixture_x86_64.xctest */; }; + FBC6553A2B3194130083E1BF /* BPLogicTestFixture_swift_x86_64.xctest in Resources */ = {isa = PBXBuildFile; fileRef = FBC655392B3194130083E1BF /* BPLogicTestFixture_swift_x86_64.xctest */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -49,6 +54,20 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + FB940A452B3032430071F2FA /* Embed Libraries */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + FB940A442B3032430071F2FA /* libBPMacTestInspector.dylib in Embed Libraries */, + ); + name = "Embed Libraries"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 0173520A2366110D008BFA4E /* BPHTMLReportWriter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BPHTMLReportWriter.h; sourceTree = ""; }; 0173520B2366110D008BFA4E /* BPHTMLReportWriter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BPHTMLReportWriter.m; sourceTree = ""; }; @@ -85,6 +104,11 @@ C4FD8C561DB6E09B000ED28C /* BPPacker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BPPacker.h; sourceTree = ""; }; C4FD8C571DB6E09B000ED28C /* BPPacker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BPPacker.m; sourceTree = ""; }; E49235FE22EA847700395D98 /* times.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = times.json; sourceTree = ""; }; + FB21F0BE2A622FFD00682AC7 /* libBPMacTestInspector.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libBPMacTestInspector.dylib; sourceTree = ""; }; + FB940A3D2B3028DD0071F2FA /* libBPTestInspector.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; path = libBPTestInspector.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; + FB940A422B3032430071F2FA /* libBPMacTestInspector.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; path = libBPMacTestInspector.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; + FBA69FA62A45F889003BADBF /* BPLogicTestFixture_x86_64.xctest */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = BPLogicTestFixture_x86_64.xctest; sourceTree = ""; }; + FBC655392B3194130083E1BF /* BPLogicTestFixture_swift_x86_64.xctest */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = BPLogicTestFixture_swift_x86_64.xctest; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -94,6 +118,7 @@ files = ( C4D6861A2267ABEF007D4237 /* bplib.framework in Frameworks */, B3109F792151F72F00B9309C /* CoreSimulator.framework in Frameworks */, + FB21F0C02A622FFE00682AC7 /* libBPMacTestInspector.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -101,6 +126,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + FB940A432B3032430071F2FA /* libBPMacTestInspector.dylib in Frameworks */, C45F9E82267E8A8800969CC3 /* bplib.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -111,6 +137,9 @@ B3380AED2150BD8600752E1B /* Frameworks */ = { isa = PBXGroup; children = ( + FB940A422B3032430071F2FA /* libBPMacTestInspector.dylib */, + FB940A3D2B3028DD0071F2FA /* libBPTestInspector.dylib */, + FB21F0BE2A622FFD00682AC7 /* libBPMacTestInspector.dylib */, C4D686192267ABEF007D4237 /* bplib.framework */, B3380AEE2150BD8700752E1B /* CoreSimulator.framework */, BA1896B821791A14000CEC36 /* XCTest.framework */, @@ -142,6 +171,8 @@ 0173521223679E87008BFA4E /* TEST-FinalReport.xml */, E49235FE22EA847700395D98 /* times.json */, BA9C2DB01DD67B66007CB967 /* testScheme.xcscheme */, + FBC655392B3194130083E1BF /* BPLogicTestFixture_swift_x86_64.xctest */, + FBA69FA62A45F889003BADBF /* BPLogicTestFixture_x86_64.xctest */, BA9C2DAE1DD674AD007CB967 /* BPSampleAppTests.xctest */, C415174C273AE3CE00646740 /* Expected-TEST-FinalReport-for-invalid-xml.xml */, ); @@ -217,6 +248,7 @@ 0137FE97237B65E100B36E69 /* Generate HTML template header */, BAEF4B301DAC539400E68294 /* Sources */, BAEF4B311DAC539400E68294 /* Frameworks */, + FB940A452B3032430071F2FA /* Embed Libraries */, ); buildRules = ( ); @@ -274,6 +306,8 @@ files = ( BA9C2DB11DD67B66007CB967 /* testScheme.xcscheme in Resources */, BA9C2DAF1DD674AD007CB967 /* BPSampleAppTests.xctest in Resources */, + FBA69FA82A45F889003BADBF /* BPLogicTestFixture_x86_64.xctest in Resources */, + FBC6553A2B3194130083E1BF /* BPLogicTestFixture_swift_x86_64.xctest in Resources */, E49235FF22EA847700395D98 /* times.json in Resources */, C415174F273AEAC400646740 /* simulator in Resources */, 0173521323679E87008BFA4E /* TEST-FinalReport.xml in Resources */, @@ -363,6 +397,10 @@ HEADER_SEARCH_PATHS = "\"$(SRCROOT)/..\""; INFOPLIST_FILE = tests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = LI.BluepillRunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -380,6 +418,10 @@ HEADER_SEARCH_PATHS = "\"$(SRCROOT)/..\""; INFOPLIST_FILE = tests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = LI.BluepillRunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -509,6 +551,10 @@ DEVELOPER_PRIVATE_FRAMEWORKS_DIR = ""; DEVELOPMENT_TEAM = 57Y47U492U; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/..\""; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); MACOSX_DEPLOYMENT_TARGET = 10.15; OTHER_LDFLAGS = ( "-weak_framework", @@ -531,6 +577,10 @@ DEVELOPER_PRIVATE_FRAMEWORKS_DIR = ""; DEVELOPMENT_TEAM = 57Y47U492U; HEADER_SEARCH_PATHS = "\"$(SRCROOT)/..\""; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); MACOSX_DEPLOYMENT_TARGET = 10.15; OTHER_LDFLAGS = ( "-weak_framework", diff --git a/bluepill/bluepill.xcodeproj/xcshareddata/xcschemes/bluepill.xcscheme b/bluepill/bluepill.xcodeproj/xcshareddata/xcschemes/bluepill.xcscheme index a8b56da3..ce415051 100644 --- a/bluepill/bluepill.xcodeproj/xcshareddata/xcschemes/bluepill.xcscheme +++ b/bluepill/bluepill.xcodeproj/xcshareddata/xcschemes/bluepill.xcscheme @@ -1,7 +1,7 @@ + version = "1.8"> @@ -62,6 +62,20 @@ ReferencedContainer = "container:../bp/bp.xcodeproj"> + + + + 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 literal 0 HcmV?d00001 diff --git a/bluepill/src/BPApp.h b/bluepill/src/BPApp.h index e48c4505..3b246210 100644 --- a/bluepill/src/BPApp.h +++ b/bluepill/src/BPApp.h @@ -8,7 +8,7 @@ // WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. #import -#import "bp/src/BPXCTestFile.h" +#import @class BPConfiguration; @interface BPApp : NSObject diff --git a/bluepill/src/BPApp.m b/bluepill/src/BPApp.m index ac134386..d3377bd0 100644 --- a/bluepill/src/BPApp.m +++ b/bluepill/src/BPApp.m @@ -8,9 +8,7 @@ // WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. #import "BPApp.h" -#import "bp/src/BPConstants.h" -#import "bp/src/BPConfiguration.h" -#import "bp/src/BPUtils.h" +#import @implementation BPApp @@ -94,6 +92,20 @@ @implementation BPApp return allXCTestFiles; } ++ (NSArray *)testsFromConfig:(BPConfiguration *)config + withError:(NSError *__autoreleasing *)errPtr { + NSMutableArray *loadedTests = [[NSMutableArray alloc] initWithCapacity:config.tests.count]; + for (NSString *testName in config.tests) { + BPTestPlan *testPlan = [config.tests objectForKey:testName]; + BPXCTestFile *xcTestFile = [BPXCTestFile BPXCTestFileFromBPTestPlan:testPlan withName:testName andError:errPtr]; + if (*errPtr) { + return nil; + } + [loadedTests addObject:xcTestFile]; + } + return loadedTests; +} + + (instancetype)appWithConfig:(BPConfiguration *)config withError:(NSError *__autoreleasing *)errPtr { @@ -102,17 +114,10 @@ + (instancetype)appWithConfig:(BPConfiguration *)config if (config.tests != nil && config.tests.count != 0) { [BPUtils printInfo:INFO withString:@"Using test bundles"]; - NSMutableArray *loadedTests = [[NSMutableArray alloc] initWithCapacity:config.tests.count]; - for (NSString *testName in config.tests) { - BPTestPlan *testPlan = [config.tests objectForKey:testName]; - BPXCTestFile *xcTestFile = [BPXCTestFile BPXCTestFileFromBPTestPlan:testPlan withName:testName andError:errPtr]; - if (*errPtr) - return nil; - [loadedTests addObject:xcTestFile]; + app.testBundles = [self testsFromConfig:config withError:errPtr]; + if (*errPtr) { + return nil; } - - app.testBundles = loadedTests; - return app; } @@ -135,6 +140,12 @@ + (instancetype)appWithConfig:(BPConfiguration *)config andTestBundlePath:config.testBundlePath andUITargetAppPath:config.testRunnerAppPath withError:errPtr]]; + } else if (config.isLogicTestTarget && config.testBundlePath) { + BPXCTestFile *testFile = [BPXCTestFile BPXCTestFileFromXCTestBundle:config.testBundlePath + andHostAppBundle:nil + andUITargetAppPath:nil + withError:errPtr]; + [allXCTestFiles addObject:testFile]; } else { BP_SET_ERROR(errPtr, @"xctestrun file must be given, see usage."); return nil; diff --git a/bluepill/src/BPHTMLReportWriter.m b/bluepill/src/BPHTMLReportWriter.m index f5057474..38ed8f67 100644 --- a/bluepill/src/BPHTMLReportWriter.m +++ b/bluepill/src/BPHTMLReportWriter.m @@ -9,7 +9,7 @@ #import #import "BPTestReportHTML.h" -#import "bp/src/BPUtils.h" +#import #import "BPHTMLReportWriter.h" diff --git a/bluepill/src/BPPacker.h b/bluepill/src/BPPacker.h index 51e8e67d..b2fdbb4c 100644 --- a/bluepill/src/BPPacker.h +++ b/bluepill/src/BPPacker.h @@ -8,8 +8,7 @@ // WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. #import -#import "bp/src/BPConfiguration.h" -#import "bp/src/BPXCTestFile.h" +#import @interface BPPacker : NSObject diff --git a/bluepill/src/BPPacker.m b/bluepill/src/BPPacker.m index 5ec0f259..ee10fabc 100644 --- a/bluepill/src/BPPacker.m +++ b/bluepill/src/BPPacker.m @@ -7,8 +7,7 @@ // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -#import "bp/src/BPXCTestFile.h" -#import "bp/src/BPUtils.h" +#import #import "BPPacker.h" @implementation BPPacker @@ -142,6 +141,7 @@ @implementation BPPacker andXCTestFiles:xcTestFiles]; NSMutableArray *bundles = [[NSMutableArray alloc] init]; + for (BPXCTestFile *xctFile in xcTestFiles) { NSArray *bundleTestsToRun = [[testsToRunByFilePath[xctFile.testBundlePath] allObjects] sortedArrayUsingSelector:@selector(compare:)]; NSNumber *estimatedBundleTime = testEstimatesByFilePath[xctFile.testBundlePath]; @@ -166,7 +166,11 @@ @implementation BPPacker i++; } // Make a bundle out of current xctFile - BPXCTestFile *bundle = [self makeBundle:xctFile withTests:bundleTestsToRun startAt:startIndex numTests:(i-startIndex) estimatedTime:[NSNumber numberWithDouble:splitExecTime]]; + NSInteger numTests = i - startIndex; + if (numTests <= 0) { + break; + } + BPXCTestFile *bundle = [self makeBundle:xctFile withTests:bundleTestsToRun startAt:startIndex numTests:numTests estimatedTime:[NSNumber numberWithDouble:splitExecTime]]; [bundles addObject:bundle]; } } diff --git a/bluepill/src/BPReportCollector.m b/bluepill/src/BPReportCollector.m index 211706d8..b67e0dd1 100644 --- a/bluepill/src/BPReportCollector.m +++ b/bluepill/src/BPReportCollector.m @@ -9,7 +9,7 @@ #import "BPHTMLReportWriter.h" #import "BPReportCollector.h" -#import "bp/src/BPUtils.h" +#import // Save path and mtime for reports (sort by mtime) @interface BPXMLReport:NSObject diff --git a/bluepill/src/BPRunner.h b/bluepill/src/BPRunner.h index 21d19f54..fd49e4e0 100644 --- a/bluepill/src/BPRunner.h +++ b/bluepill/src/BPRunner.h @@ -8,8 +8,7 @@ // WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. #import -#import "bp/src/BPXCTestFile.h" -#import "bp/src/BPConfiguration.h" +#import @interface BPRunner : NSObject diff --git a/bluepill/src/BPRunner.m b/bluepill/src/BPRunner.m index 5fdf6162..d013a73b 100644 --- a/bluepill/src/BPRunner.m +++ b/bluepill/src/BPRunner.m @@ -8,12 +8,7 @@ // WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. #import -#import "bp/src/BPCreateSimulatorHandler.h" -#import "bp/src/BPSimulator.h" -#import "bp/src/BPStats.h" -#import "bp/src/BPUtils.h" -#import "bp/src/BPWaitTimer.h" -#import "bp/src/SimulatorHelper.h" +#import #import "BPPacker.h" #import "BPRunner.h" #import "BPSwimlane.h" @@ -132,12 +127,14 @@ - (int)runWithBPXCTestFiles:(NSArray *)xcTestFiles { [BPUtils printInfo:ERROR withString:@"Packing failed: %@", [error localizedDescription]]; return 1; } + if (bundles.count < numSims) { [BPUtils printInfo:WARNING withString:@"Lowering number of parallel simulators from %lu to %lu because there aren't enough test bundles.", numSims, bundles.count]; numSims = bundles.count; } + if (self.config.cloneSimulator) { self.testHostSimTemplates = [bpSimulator createSimulatorAndInstallAppWithBundles:xcTestFiles]; if ([self.testHostSimTemplates count] == 0) { @@ -172,6 +169,7 @@ - (int)runWithBPXCTestFiles:(NSArray *)xcTestFiles { return -1; } } + while (1) { if (interrupted) { if (interrupted >=5) { @@ -222,6 +220,8 @@ - (int)runWithBPXCTestFiles:(NSArray *)xcTestFiles { [bundles removeObjectAtIndex:0]; } } + + // Resume in a second. Every 30 seconds, log status. sleep(1); if (seconds % 30 == 0) { NSString *listString; @@ -251,6 +251,7 @@ - (int)runWithBPXCTestFiles:(NSArray *)xcTestFiles { [self addCounters]; } + // Cleanup Devices for (int i = 0; i < [deviceList count]; i++) { NSTask *task = [self newTaskToDeleteDevice:[deviceList objectAtIndex:i] andNumber:i+1]; [task launch]; diff --git a/bluepill/src/BPSwimlane.h b/bluepill/src/BPSwimlane.h index 4bcfd60f..dd2f6a9e 100644 --- a/bluepill/src/BPSwimlane.h +++ b/bluepill/src/BPSwimlane.h @@ -8,8 +8,7 @@ // WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. #import -#import "bp/src/BPXCTestFile.h" -#import "bp/src/BPConfiguration.h" +#import @interface BPSwimlane : NSObject diff --git a/bluepill/src/BPSwimlane.m b/bluepill/src/BPSwimlane.m index 024020fd..36ed0bf3 100644 --- a/bluepill/src/BPSwimlane.m +++ b/bluepill/src/BPSwimlane.m @@ -7,7 +7,7 @@ // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -#import "bp/src/BPUtils.h" +#import #import "BPSwimlane.h" @interface BPSwimlane() diff --git a/bluepill/src/main.m b/bluepill/src/main.m index 0bd4884e..ff897113 100644 --- a/bluepill/src/main.m +++ b/bluepill/src/main.m @@ -10,13 +10,10 @@ #import #import #import -#import "bp/src/BPConfiguration.h" -#import "bp/src/BPStats.h" -#import "bp/src/BPUtils.h" -#import "bp/src/BPWriter.h" #import "BPApp.h" #import "BPReportCollector.h" #import "BPRunner.h" +#import #include #include diff --git a/bluepill/tests/BPAppTests.m b/bluepill/tests/BPAppTests.m index 467978dc..782425e0 100644 --- a/bluepill/tests/BPAppTests.m +++ b/bluepill/tests/BPAppTests.m @@ -9,11 +9,7 @@ #import #import "bluepill/src/BPApp.h" -#import "bp/src/BPConfiguration.h" -#import "bp/src/BPXCTestFile.h" -#import "bp/src/BPUtils.h" -#import "bp/src/BPConstants.h" -#import "bp/tests/BPTestHelper.h" +#import @interface BPAppTests : XCTestCase @property (nonatomic, strong) BPConfiguration* config; diff --git a/bluepill/tests/BPCLITests.m b/bluepill/tests/BPCLITests.m index ae51d43d..b9d27aa9 100644 --- a/bluepill/tests/BPCLITests.m +++ b/bluepill/tests/BPCLITests.m @@ -8,9 +8,7 @@ // WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. #import -#import "bp/src/BPConfiguration.h" -#import "bp/src/BPUtils.h" -#import "bp/tests/BPTestHelper.h" +#import @interface BPCLITests : XCTestCase diff --git a/bluepill/tests/BPIntegrationTests.m b/bluepill/tests/BPIntegrationTests.m index 84dbe4d2..5c396f82 100644 --- a/bluepill/tests/BPIntegrationTests.m +++ b/bluepill/tests/BPIntegrationTests.m @@ -7,14 +7,12 @@ // #import -#import "bp/tests/BPTestHelper.h" -#import "bp/src/BPConfiguration.h" -#import "bp/src/BPUtils.h" #import "bluepill/src/BPRunner.h" #import "bluepill/src/BPApp.h" #import "bluepill/src/BPPacker.h" -#import "bp/src/BPXCTestFile.h" -#import "bp/src/BPConstants.h" + +#import +#import @interface BPIntegrationTests : XCTestCase @end @@ -59,6 +57,124 @@ - (void)tearDown { [super tearDown]; } +- (void)testArchitecture_x86_64 { + NSString *bundlePath = BPTestHelper.logicTestBundlePath_x86_64; + + BPConfiguration *config = [BPTestUtils makeUnhostedTestConfiguration]; + config.stuckTimeout = @(2); + config.testCaseTimeout = @(10); + // Test multiple test bundles, while skipping any failing tests so that we + // can still validate that we get a success code.. + config.testBundlePath = bundlePath; + + NSString *bpPath = [BPTestHelper bpExecutablePath]; + BPRunner *runner = [BPRunner BPRunnerWithConfig:config withBpPath:bpPath]; + NSError *error; + BPApp *app = [BPApp appWithConfig:config withError:&error]; + + XCTAssert(runner != nil); + int rc = [runner runWithBPXCTestFiles:app.testBundles]; + XCTAssert(rc == 0, @"Wanted 0, got %d", rc); + XCTAssert([runner busySwimlaneCount] == 0); +} + +- (void)testArchitectureWithSwiftTests_x86_64 { + NSString *bundlePath = BPTestHelper.logicTestBundlePath_swift_x86_64; + + BPConfiguration *config = [BPTestUtils makeUnhostedTestConfiguration]; + config.stuckTimeout = @(2); + config.testCaseTimeout = @(10); + // Test multiple test bundles, while skipping any failing tests so that we + // can still validate that we get a success code.. + config.testBundlePath = bundlePath; + + NSString *bpPath = [BPTestHelper bpExecutablePath]; + BPRunner *runner = [BPRunner BPRunnerWithConfig:config withBpPath:bpPath]; + NSError *error; + BPApp *app = [BPApp appWithConfig:config withError:&error]; + + XCTAssert(runner != nil); + int rc = [runner runWithBPXCTestFiles:app.testBundles]; + XCTAssert(rc == 0, @"Wanted 0, got %d", rc); + XCTAssert([runner busySwimlaneCount] == 0); +} + +// Currently having troubles generating a new arm64 fixture. +- (void)DISABLEDtestArchitecture_arm64 { + NSString *bundlePath = BPTestHelper.logicTestBundlePath_arm64; + + BPConfiguration *config = [BPTestUtils makeUnhostedTestConfiguration]; + config.stuckTimeout = @(2); + config.testCaseTimeout = @(10); + config.numSims = @(1); + // Test multiple test bundles, while skipping any failing tests so that we + // can still validate that we get a success code.. + config.testBundlePath = bundlePath; + + NSString *bpPath = [BPTestHelper bpExecutablePath]; + BPRunner *runner = [BPRunner BPRunnerWithConfig:config withBpPath:bpPath]; + NSError *error; + BPApp *app = [BPApp appWithConfig:config withError:&error]; + + XCTAssert(runner != nil); + int rc = [runner runWithBPXCTestFiles:app.testBundles]; + XCTAssert(rc == 0, @"Wanted 0, got %d", rc); + XCTAssert([runner busySwimlaneCount] == 0); +} + +- (void)testLogicTestBundles { + BPConfiguration *config = [BPTestUtils makeUnhostedTestConfiguration]; + config.stuckTimeout = @(2); + config.testCaseTimeout = @(10); + // Test multiple test bundles, while skipping any failing tests so that we + // can still validate that we get a success code.. + config.testBundlePath = BPTestHelper.logicTestBundlePath; + config.additionalUnitTestBundles = @[BPTestHelper.logicTestBundlePath]; + config.testCasesToSkip = @[ + @"BPLogicTests/testFailingLogicTest", + @"BPLogicTests/testCrashTestCaseLogicTest", + @"BPLogicTests/testCrashExecutionLogicTest", + @"BPLogicTests/testStuckLogicTest", + @"BPLogicTests/testSlowLogicTest", + ]; + + NSString *bpPath = [BPTestHelper bpExecutablePath]; + BPRunner *runner = [BPRunner BPRunnerWithConfig:config withBpPath:bpPath]; + NSError *error; + BPApp *app = [BPApp appWithConfig:config withError:&error]; + + XCTAssert(runner != nil); + int rc = [runner runWithBPXCTestFiles:app.testBundles]; + XCTAssert(rc == 0, @"Wanted 0, got %d", rc); + XCTAssert([runner busySwimlaneCount] == 0); +} + +- (void)testTwoBPInstancesWithLogicTestPlanJson { + [self writeLogicTestPlan]; + BPConfiguration *config = [BPTestUtils makeUnhostedTestConfiguration]; + config.numSims = @2; + config.testBundlePath = nil; + config.testRunnerAppPath = nil; + config.appBundlePath = nil; + config.testPlanPath = [BPTestHelper testPlanPath]; + + NSError *err; + [config validateConfigWithError:&err]; + BPApp *app = [BPApp appWithConfig:config withError:&err]; + NSString *bpPath = [BPTestHelper bpExecutablePath]; + BPRunner *runner = [BPRunner BPRunnerWithConfig:config withBpPath:bpPath]; + XCTAssert(runner != nil); + int rc = [runner runWithBPXCTestFiles:app.testBundles]; + XCTAssert(rc != 0); // this runs tests that fail + XCTAssertEqual(app.testBundles.count, 2); + XCTAssertTrue([app.testBundles[0].name isEqualToString:@"BPLogicTests"]); + XCTAssertEqual(app.testBundles[0].numTests, 10); + XCTAssertEqual(app.testBundles[0].skipTestIdentifiers.count, 0); + XCTAssertTrue([app.testBundles[1].name isEqualToString:@"BPPassingLogicTests"]); + XCTAssertEqual(app.testBundles[1].numTests, 207); + XCTAssertEqual(app.testBundles[1].skipTestIdentifiers.count, 0); +} + - (void)testOneBPInstance { BPConfiguration *config = [self generateConfig]; config.numSims = @1; @@ -91,6 +207,8 @@ - (void)testTwoBPInstances { XCTAssert([runner busySwimlaneCount] == 0); } +// Note: If this is failing for you locally, try resetting all of your +// sims with `sudo rm -rf /private/tmp/com.apple.CoreSimulator.SimDevice.*` - (void)testClonedSimulators { BPConfiguration *config = [self generateConfig]; config.numSims = @2; @@ -150,13 +268,23 @@ - (void)testTwoBPInstancesWithXCTestRunFile { BPRunner *runner = [BPRunner BPRunnerWithConfig:config withBpPath:bpPath]; XCTAssert(runner != nil); int rc = [runner runWithBPXCTestFiles:app.testBundles]; + + // Set this once we learn how many tests are in the BPSampleAppTests bundle. + NSInteger sampleAppTestsRemaining = NSNotFound; for (BPXCTestFile *testBundle in app.testBundles) { + // BPSampleAppTests is a huge bundle and will be broken into multiple batches + // Across all of these batches, the NOT skipped tests should add up to the total + // test count. if ([testBundle.name isEqualToString:@"BPSampleAppTests"]) { - XCTAssertEqual(testBundle.skipTestIdentifiers.count, 8); + if (sampleAppTestsRemaining == NSNotFound) { + sampleAppTestsRemaining = testBundle.allTestCases.count; + } + sampleAppTestsRemaining -= (testBundle.allTestCases.count - testBundle.skipTestIdentifiers.count); } else { XCTAssertEqual(testBundle.skipTestIdentifiers.count, 0); } } + XCTAssertEqual(sampleAppTestsRemaining, 0); XCTAssert(rc != 0); // this runs tests that fail } @@ -224,6 +352,25 @@ - (void)writeTestPlan { [jsonData writeToFile:[BPTestHelper testPlanPath] atomically:YES]; } +- (void)writeLogicTestPlan { + NSDictionary *testPlan = @{ + @"tests": @{ + @"BPLogicTests": @{ + @"test_bundle_path": [BPTestHelper logicTestBundlePath], + @"environment": @{}, + @"arguments": @{} + }, + @"BPPassingLogicTests": @{ + @"test_bundle_path": [BPTestHelper passingLogicTestBundlePath], + @"environment": @{}, + @"arguments": @{} + } + } + }; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:testPlan options:0 error:nil]; + [jsonData writeToFile:[BPTestHelper testPlanPath] atomically:YES]; +} + // TODO: Enable this when we figure out issue #469 - (void)DISABLE_testTwoBPInstancesWithVideo { NSFileManager *fileManager = [NSFileManager defaultManager]; diff --git a/bluepill/tests/BPPackerTests.m b/bluepill/tests/BPPackerTests.m index 98826a73..ed67343d 100644 --- a/bluepill/tests/BPPackerTests.m +++ b/bluepill/tests/BPPackerTests.m @@ -10,11 +10,7 @@ #import "bluepill/src/BPRunner.h" #import "bluepill/src/BPApp.h" #import "bluepill/src/BPPacker.h" -#import "bp/tests/BPTestHelper.h" -#import "bp/src/BPConfiguration.h" -#import "bp/src/BPUtils.h" -#import "bp/src/BPXCTestFile.h" -#import "bp/src/BPConstants.h" +#import @interface BPPackerTests : XCTestCase @property (nonatomic, strong) BPConfiguration* config; diff --git a/bluepill/tests/BPRunnerTests.m b/bluepill/tests/BPRunnerTests.m index 37c4f0cc..a28489bd 100644 --- a/bluepill/tests/BPRunnerTests.m +++ b/bluepill/tests/BPRunnerTests.m @@ -9,13 +9,10 @@ #import #import "bp/tests/BPTestHelper.h" -#import "bp/src/BPConfiguration.h" -#import "bp/src/BPUtils.h" +#import #import "bluepill/src/BPRunner.h" #import "bluepill/src/BPApp.h" #import "bluepill/src/BPPacker.h" -#import "bp/src/BPXCTestFile.h" -#import "bp/src/BPConstants.h" @interface BPRunnerTests : XCTestCase @property (nonatomic, strong) BPConfiguration* config; diff --git a/bluepill/tests/Resource Files/BPLogicTestFixture_swift_x86_64.xctest/BPLogicTestFixture_swift_x86_64 b/bluepill/tests/Resource Files/BPLogicTestFixture_swift_x86_64.xctest/BPLogicTestFixture_swift_x86_64 new file mode 100755 index 0000000000000000000000000000000000000000..06fe278be44edbf8801ce637428a25c2db656414 GIT binary patch literal 376352 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 C$sK0^ literal 0 HcmV?d00001 diff --git a/bluepill/tests/Resource Files/BPLogicTestFixture_swift_x86_64.xctest/Info.plist b/bluepill/tests/Resource Files/BPLogicTestFixture_swift_x86_64.xctest/Info.plist new file mode 100644 index 0000000000000000000000000000000000000000..a667de3fa4b371c57c9408121006e66e760f9e97 GIT binary patch literal 750 zcmZ8dNpI6Y6rMM11zIv~3Z;cEl(iSjPOKEc3D*P#(l)K#u&9dVI1^{Yc#)Tq7JdLX zegLOlxgaEt{0a_S`2$FB;efa>No}QKF7Lf>nVI*sL(bwvmcNr}z$Z?gI(=sB?78#f z6Bi0(a;i9e@zTuY*_pX3R~HtSt}QQJzj5={ZHpAOl=05G?ev&WH;v(Bk=eDXmiiq| zb-G7+5PH;4wyDbk-y%~v_pnc$G_l*99s_F~A!S`gBa6)E%9`H|bmk<&Z%6x08pWd9 z&Djmx6E_s|v0*zeZI_z+p+M(y)aV6Kk~xheV!nHvOv6ynwA0{r(hVXnwn!m&W~LXq zxfXaKehIz`}t&&h4gB5i}|O2M;Oi-~0o>8|YO4 literal 0 HcmV?d00001 diff --git a/bluepill/tests/Resource Files/BPLogicTestFixture_swift_x86_64.xctest/_CodeSignature/CodeResources b/bluepill/tests/Resource Files/BPLogicTestFixture_swift_x86_64.xctest/_CodeSignature/CodeResources new file mode 100644 index 00000000..a6cbde0f --- /dev/null +++ b/bluepill/tests/Resource Files/BPLogicTestFixture_swift_x86_64.xctest/_CodeSignature/CodeResources @@ -0,0 +1,101 @@ + + + + + files + + Info.plist + + DT7gNfddwFlosavksp+OY+Ga3e0= + + + files2 + + rules + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/bluepill/tests/Resource Files/BPLogicTestFixture_x86_64.xctest/BPLogicTestFixture_x86_64 b/bluepill/tests/Resource Files/BPLogicTestFixture_x86_64.xctest/BPLogicTestFixture_x86_64 new file mode 100755 index 0000000000000000000000000000000000000000..42f14694b97dadd3c82153b51728628689317c88 GIT binary patch literal 71616 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 B|8f8T literal 0 HcmV?d00001 diff --git a/bluepill/tests/Resource Files/BPLogicTestFixture_x86_64.xctest/Info.plist b/bluepill/tests/Resource Files/BPLogicTestFixture_x86_64.xctest/Info.plist new file mode 100644 index 0000000000000000000000000000000000000000..78869ce642c5f936f755545a82e914fb44121fb7 GIT binary patch literal 744 zcmZWl%Wl&^6rC9;lmgAPX`w9?N`L|z%MKxBSzI?#Xxf_C4G)!Cjy*{R#vaR$kbFab zfNi#{_=@cytP=ledq4I`%8yg4K?vyM)quvywprX+evTFHS>kTUGggH zs0n4Rg_nz2jSLCnfk*tfP5PAk7A_T7hlEkqDQB!*+Zj-w>>JtZNHcY2$2}f;=`R*8 z7c|{=-r9X)jswwMDZ0jhhjFfG#3A+je}hR72$;BqWMtvhg4y(YT%%4btk~iBkE!_r z-yRVsiR~^+SKQ)WKp6=wjCZL|y~Nu${--ouu`lgW5*{5iMFi9#4cnt^Y~i`ny>i}R z9$+3_>2#n7Q`K9%PaTs)af6QHBqY}8aowudL@G2GaGylNiAalwN?nporKZRSRaxCo z+QfgRbgI?uYF!b@|1)f=ds@p-Bzdxyf_kcC6iS9rvT()Wo@56BBa*mft49)JLr-#; z?e`_cO23m*C#|NW>W1x!*(X7envEvw7@Dc2j#5)=8(lCB9)eBq6tux>Fb2n90zQHh z@D&2M1|PvqXutt{2an-<_yL~4Pw)%;2EW4}@F$u=GiVlJR6*-#2kEGX2IvjqAUg>D MuHZpl^MZ!I0E%Yg$p8QV literal 0 HcmV?d00001 diff --git a/bluepill/tests/Resource Files/BPLogicTestFixture_x86_64.xctest/_CodeSignature/CodeResources b/bluepill/tests/Resource Files/BPLogicTestFixture_x86_64.xctest/_CodeSignature/CodeResources new file mode 100644 index 00000000..00ca198f --- /dev/null +++ b/bluepill/tests/Resource Files/BPLogicTestFixture_x86_64.xctest/_CodeSignature/CodeResources @@ -0,0 +1,101 @@ + + + + + files + + Info.plist + + a2X62OSUA6yDTeQ0W3o6GtPmrKc= + + + files2 + + rules + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/bp/bp.xcodeproj/project.pbxproj b/bp/bp.xcodeproj/project.pbxproj index ccbf023c..3e64e8de 100644 --- a/bp/bp.xcodeproj/project.pbxproj +++ b/bp/bp.xcodeproj/project.pbxproj @@ -69,7 +69,7 @@ BA0096FE1DCA5D810000DD45 /* testConfigRelativePath.json in Resources */ = {isa = PBXBuildFile; fileRef = BA0096FD1DCA5D810000DD45 /* testConfigRelativePath.json */; }; BA0097001DCA61210000DD45 /* BPConfigurationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BA0096FF1DCA61210000DD45 /* BPConfigurationTests.m */; }; BA0C554D1DDAE241009E1377 /* failure_retry_report.xml in Resources */ = {isa = PBXBuildFile; fileRef = BA0C554C1DDAE241009E1377 /* failure_retry_report.xml */; }; - BA1809B81DB89B5600D7D130 /* BluepillTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BA1809B71DB89B5600D7D130 /* BluepillTests.m */; }; + BA1809B81DB89B5600D7D130 /* BluepillHostedTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BA1809B71DB89B5600D7D130 /* BluepillHostedTests.m */; }; BA180A091DBB00FA00D7D130 /* BPUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BA180A081DBB00FA00D7D130 /* BPUtilsTests.m */; }; BA180A0C1DBB011200D7D130 /* testConfig.json in Resources */ = {isa = PBXBuildFile; fileRef = BA180A0B1DBB011200D7D130 /* testConfig.json */; }; BA180A141DBB088100D7D130 /* testScheme.xcscheme in Resources */ = {isa = PBXBuildFile; fileRef = BA180A131DBB088100D7D130 /* testScheme.xcscheme */; }; @@ -109,6 +109,19 @@ C4F08F75224C45750001AD2A /* BPExitStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A4D7A851DDBB156001E085D /* BPExitStatus.m */; }; C4FAC2951E5E67ED00ACC5D9 /* testConfig-busted.json in Resources */ = {isa = PBXBuildFile; fileRef = C4FAC2941E5E67ED00ACC5D9 /* testConfig-busted.json */; }; C94DE0BB4360016D3D3061D9 /* simulator-preferences.plist in Resources */ = {isa = PBXBuildFile; fileRef = C94DEF7F8BCA7AB3C9114467 /* simulator-preferences.plist */; }; + FB1C695629A6E58500BFDFB4 /* BluepillUnhostedTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FB1C695529A6E58500BFDFB4 /* BluepillUnhostedTests.m */; }; + FB21F0BB2A622FF600682AC7 /* libBPMacTestInspector.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FB21F0BA2A622FF600682AC7 /* libBPMacTestInspector.dylib */; }; + FB21F0BC2A622FF600682AC7 /* libBPMacTestInspector.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FB21F0BA2A622FF600682AC7 /* libBPMacTestInspector.dylib */; }; + FB21F0BD2A622FF600682AC7 /* libBPMacTestInspector.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FB21F0BA2A622FF600682AC7 /* libBPMacTestInspector.dylib */; }; + FB21F0DE2A65103F00682AC7 /* BPTestCaseInfoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FB21F0DD2A65103F00682AC7 /* BPTestCaseInfoTests.m */; }; + FB53751F2A66288300496432 /* BPTestInspectionHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = FB53751D2A66288300496432 /* BPTestInspectionHandler.h */; }; + FB5375202A66288300496432 /* BPTestInspectionHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = FB53751E2A66288300496432 /* BPTestInspectionHandler.m */; }; + FB5375212A66288300496432 /* BPTestInspectionHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = FB53751E2A66288300496432 /* BPTestInspectionHandler.m */; }; + FB5375242A66494100496432 /* libBPTestInspector.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FB5375222A66494000496432 /* libBPTestInspector.dylib */; }; + FBBBD8EF29A06AF6002B9115 /* BPTestUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = FBBBD8ED29A06AF6002B9115 /* BPTestUtils.m */; }; + FBD772BF2A33E1DA0098CEFD /* BluepillUnhostedBatchingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FBD772BE2A33E1DA0098CEFD /* BluepillUnhostedBatchingTests.m */; }; + FBD772C12A3452240098CEFD /* BPTestUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = FBBBD8EE29A06AF6002B9115 /* BPTestUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + FBD772C22A3453010098CEFD /* BPTestUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = FBBBD8ED29A06AF6002B9115 /* BPTestUtils.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -128,6 +141,19 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + FB9F19452A32F0EF00C894D3 /* Embed Libraries */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Libraries"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 018D5C1025B4FF4200B0314B /* BPIntTestCase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BPIntTestCase.h; sourceTree = ""; }; 018D5C1125B4FF4200B0314B /* BPIntTestCase.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BPIntTestCase.m; sourceTree = ""; }; @@ -158,7 +184,6 @@ 7A7901821D5CB679004D4325 /* bp */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = bp; sourceTree = BUILT_PRODUCTS_DIR; }; 7A7901851D5CB679004D4325 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 7A79018C1D5CF113004D4325 /* bp.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = bp.xcconfig; sourceTree = ""; }; - 7A79018E1D5D136F004D4325 /* CoreSimulator.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreSimulator.framework; path = Library/PrivateFrameworks/CoreSimulator.framework; sourceTree = DEVELOPER_DIR; }; 7A7D66001DB679820015C937 /* BPStats.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BPStats.h; sourceTree = ""; }; 7A7D66011DB679820015C937 /* BPStats.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BPStats.m; sourceTree = ""; }; 7A7E7BAE1DF2066B007928F3 /* BPHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BPHandler.h; sourceTree = ""; }; @@ -193,12 +218,11 @@ BA0096FF1DCA61210000DD45 /* BPConfigurationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BPConfigurationTests.m; sourceTree = ""; }; BA0097011DCA626F0000DD45 /* BPConfiguration+Test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "BPConfiguration+Test.h"; sourceTree = ""; }; BA0C554C1DDAE241009E1377 /* failure_retry_report.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = failure_retry_report.xml; sourceTree = ""; }; - BA1809B71DB89B5600D7D130 /* BluepillTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BluepillTests.m; sourceTree = ""; }; + BA1809B71DB89B5600D7D130 /* BluepillHostedTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BluepillHostedTests.m; sourceTree = ""; }; BA1809BC1DB89B8B00D7D130 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/AppKit.framework; sourceTree = DEVELOPER_DIR; }; BA180A081DBB00FA00D7D130 /* BPUtilsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BPUtilsTests.m; sourceTree = ""; }; BA180A0B1DBB011200D7D130 /* testConfig.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = testConfig.json; sourceTree = ""; }; BA180A131DBB088100D7D130 /* testScheme.xcscheme */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = testScheme.xcscheme; sourceTree = ""; }; - BA1896B321791683000CEC36 /* CoreSimulator */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = CoreSimulator; path = ../../../../../Library/Developer/PrivateFrameworks/CoreSimulator.framework/Versions/A/CoreSimulator; sourceTree = ""; }; BA1896B6217916F8000CEC36 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/MacOSX.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; BA1949341E4AF82F00881887 /* BPTMDRunnerConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BPTMDRunnerConnection.h; sourceTree = ""; }; BA1949351E4AF82F00881887 /* BPTMDRunnerConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BPTMDRunnerConnection.m; sourceTree = ""; }; @@ -292,9 +316,6 @@ BAFCCA541E36DBA900E33C31 /* DTXTransport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DTXTransport.h; sourceTree = ""; }; BAFCCA581E36DBA900E33C31 /* NSURLSessionDelegate-Protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURLSessionDelegate-Protocol.h"; sourceTree = ""; }; BAFCCA6D1E36EB5500E33C31 /* XCTestManager_IDEInterface-Protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCTestManager_IDEInterface-Protocol.h"; sourceTree = ""; }; - BAFCCA6E1E36EB5500E33C31 /* XCTestManager_ManagerInterface-Protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCTestManager_ManagerInterface-Protocol.h"; sourceTree = ""; }; - BAFCCA6F1E36EB5500E33C31 /* XCTestManager_TestsInterface-Protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCTestManager_TestsInterface-Protocol.h"; sourceTree = ""; }; - BAFCCA701E37298B00E33C31 /* XCTestDriverInterface-Protocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XCTestDriverInterface-Protocol.h"; sourceTree = ""; }; BAFCCA711E37298B00E33C31 /* XCTestManager_DaemonConnectionInterface-Protocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XCTestManager_DaemonConnectionInterface-Protocol.h"; sourceTree = ""; }; C41A2C701E0B2497005D9751 /* BPXCTestFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BPXCTestFile.h; sourceTree = ""; }; C41A2C711E0B2497005D9751 /* BPXCTestFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = BPXCTestFile.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; @@ -314,6 +335,15 @@ C4AF1ADE2273649500618F0B /* BPVersion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BPVersion.h; sourceTree = ""; }; C4FAC2941E5E67ED00ACC5D9 /* testConfig-busted.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "testConfig-busted.json"; sourceTree = ""; }; C94DEF7F8BCA7AB3C9114467 /* simulator-preferences.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = "simulator-preferences.plist"; sourceTree = ""; }; + FB1C695529A6E58500BFDFB4 /* BluepillUnhostedTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BluepillUnhostedTests.m; sourceTree = ""; }; + FB21F0BA2A622FF600682AC7 /* libBPMacTestInspector.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libBPMacTestInspector.dylib; sourceTree = ""; }; + FB21F0DD2A65103F00682AC7 /* BPTestCaseInfoTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BPTestCaseInfoTests.m; sourceTree = ""; }; + FB53751D2A66288300496432 /* BPTestInspectionHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BPTestInspectionHandler.h; sourceTree = ""; }; + FB53751E2A66288300496432 /* BPTestInspectionHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BPTestInspectionHandler.m; sourceTree = ""; }; + FB5375222A66494000496432 /* libBPTestInspector.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libBPTestInspector.dylib; sourceTree = ""; }; + FBBBD8ED29A06AF6002B9115 /* BPTestUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BPTestUtils.m; sourceTree = ""; }; + FBBBD8EE29A06AF6002B9115 /* BPTestUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BPTestUtils.h; sourceTree = ""; }; + FBD772BE2A33E1DA0098CEFD /* BluepillUnhostedBatchingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BluepillUnhostedBatchingTests.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -321,6 +351,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + FB21F0BB2A622FF600682AC7 /* libBPMacTestInspector.dylib in Frameworks */, C487CB1C226A663C00CC5BC2 /* bplib.framework in Frameworks */, B324B91D1F280AD100AAE2BC /* CoreSimulator.framework in Frameworks */, 7AB913001D5E209800621608 /* AppKit.framework in Frameworks */, @@ -332,7 +363,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + FB21F0BD2A622FF600682AC7 /* libBPMacTestInspector.dylib in Frameworks */, BA1896B5217916A5000CEC36 /* CoreSimulator.framework in Frameworks */, + FB5375242A66494100496432 /* libBPTestInspector.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -340,6 +373,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + FB21F0BC2A622FF600682AC7 /* libBPMacTestInspector.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -349,12 +383,10 @@ 7A4FB8CC1DF89A790073F268 /* src */ = { isa = PBXGroup; children = ( - C487CB1A226A36F500CC5BC2 /* BPVersion.h */, BA1949341E4AF82F00881887 /* BPTMDRunnerConnection.h */, BA1949351E4AF82F00881887 /* BPTMDRunnerConnection.m */, BA1949381E4AF83E00881887 /* BPTMDControlConnection.h */, BA1949391E4AF83E00881887 /* BPTMDControlConnection.m */, - BA954F551D6D1AB3007D011D /* PrivateHeaders */, BA34F5E21D6D75E30063B17F /* SimulatorHelper.h */, BA34F5E31D6D75E30063B17F /* SimulatorHelper.m */, 7A4933131DAD63A50060D54F /* SimulatorMonitor.h */, @@ -393,6 +425,8 @@ 7A7E7BAF1DF2066B007928F3 /* BPHandler.m */, 7A7E7BB11DF20F81007928F3 /* BPApplicationLaunchHandler.h */, 7A7E7BB21DF20F81007928F3 /* BPApplicationLaunchHandler.m */, + FB53751D2A66288300496432 /* BPTestInspectionHandler.h */, + FB53751E2A66288300496432 /* BPTestInspectionHandler.m */, 7A7E7BB61DF2173C007928F3 /* BPCreateSimulatorHandler.h */, 7A7E7BB71DF2173C007928F3 /* BPCreateSimulatorHandler.m */, 7A7E7BBA1DF21749007928F3 /* BPDeleteSimulatorHandler.h */, @@ -403,8 +437,10 @@ BA944BCF1D76A39A00A4BDA3 /* Bluepill.m */, 7A7E7BBE1DF22CE1007928F3 /* BPExecutionContext.h */, 7A7E7BBF1DF22CE1007928F3 /* BPExecutionContext.m */, - B368E55D213F8D2E00B4DEA3 /* Info.plist */, 7A7901851D5CB679004D4325 /* main.m */, + BA954F551D6D1AB3007D011D /* PrivateHeaders */, + C487CB1A226A36F500CC5BC2 /* BPVersion.h */, + B368E55D213F8D2E00B4DEA3 /* Info.plist */, ); path = src; sourceTree = ""; @@ -435,13 +471,13 @@ 7A79018D1D5D136F004D4325 /* Frameworks */ = { isa = PBXGroup; children = ( + FB5375222A66494000496432 /* libBPTestInspector.dylib */, + FB21F0BA2A622FF600682AC7 /* libBPMacTestInspector.dylib */, C47411F7264F4C3F000F84D1 /* XcodeKit.framework */, BA1896B6217916F8000CEC36 /* XCTest.framework */, - BA1896B321791683000CEC36 /* CoreSimulator */, B324B91C1F280AD100AAE2BC /* CoreSimulator.framework */, BA1809BC1DB89B8B00D7D130 /* AppKit.framework */, 7AB912FE1D5E209800621608 /* AppKit.framework */, - 7A79018E1D5D136F004D4325 /* CoreSimulator.framework */, 7A58D2E51D62689B002F6B6D /* DVTFoundation.framework */, 7AB912FF1D5E209800621608 /* Foundation.framework */, ); @@ -491,11 +527,8 @@ BA34F5E51D6D78120063B17F /* XCTest */ = { isa = PBXGroup; children = ( - BAFCCA701E37298B00E33C31 /* XCTestDriverInterface-Protocol.h */, BAFCCA711E37298B00E33C31 /* XCTestManager_DaemonConnectionInterface-Protocol.h */, BAFCCA6D1E36EB5500E33C31 /* XCTestManager_IDEInterface-Protocol.h */, - BAFCCA6E1E36EB5500E33C31 /* XCTestManager_ManagerInterface-Protocol.h */, - BAFCCA6F1E36EB5500E33C31 /* XCTestManager_TestsInterface-Protocol.h */, BA34F5E61D6D78120063B17F /* XCTestConfiguration.h */, ); path = XCTest; @@ -562,8 +595,10 @@ BAB24F691DB5DB2300867756 /* tests */ = { isa = PBXGroup; children = ( + FBBBD8EC29A06AE7002B9115 /* Utils */, BA180A0A1DBB011200D7D130 /* Resource Files */, - BA1809B71DB89B5600D7D130 /* BluepillTests.m */, + BA1809B71DB89B5600D7D130 /* BluepillHostedTests.m */, + FBD772C02A33E1E20098CEFD /* Unhosted Tests */, C467E5491DC930D200BC80EE /* BPCLITests.m */, BA0097011DCA626F0000DD45 /* BPConfiguration+Test.h */, BA0096FF1DCA61210000DD45 /* BPConfigurationTests.m */, @@ -618,6 +653,25 @@ path = DTXConnectionServices; sourceTree = ""; }; + FBBBD8EC29A06AE7002B9115 /* Utils */ = { + isa = PBXGroup; + children = ( + FBBBD8EE29A06AF6002B9115 /* BPTestUtils.h */, + FBBBD8ED29A06AF6002B9115 /* BPTestUtils.m */, + ); + path = Utils; + sourceTree = ""; + }; + FBD772C02A33E1E20098CEFD /* Unhosted Tests */ = { + isa = PBXGroup; + children = ( + FB21F0DD2A65103F00682AC7 /* BPTestCaseInfoTests.m */, + FB1C695529A6E58500BFDFB4 /* BluepillUnhostedTests.m */, + FBD772BE2A33E1DA0098CEFD /* BluepillUnhostedBatchingTests.m */, + ); + path = "Unhosted Tests"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -635,6 +689,8 @@ B3520CE2214C5DE3003DC68F /* BPDeleteSimulatorHandler.h in Headers */, B368E579213F965600B4DEA3 /* BPTestCase.h in Headers */, B368E57A213F965600B4DEA3 /* BPTestClass.h in Headers */, + FBD772C12A3452240098CEFD /* BPTestUtils.h in Headers */, + FB53751F2A66288300496432 /* BPTestInspectionHandler.h in Headers */, B368E57B213F965600B4DEA3 /* BPUtils.h in Headers */, B368E57C213F965600B4DEA3 /* BPXCTestFile.h in Headers */, B368E571213F8E8F00B4DEA3 /* BPConstants.h in Headers */, @@ -679,6 +735,7 @@ B368E556213F8D2E00B4DEA3 /* Frameworks */, B368E557213F8D2E00B4DEA3 /* Headers */, B368E558213F8D2E00B4DEA3 /* Resources */, + FB9F19452A32F0EF00C894D3 /* Embed Libraries */, ); buildRules = ( ); @@ -828,6 +885,7 @@ BA19493A1E4AF83E00881887 /* BPTMDControlConnection.m in Sources */, 7ADBB1471DCBBC0E00DC4E8D /* BPTreeAssembler.m in Sources */, 7A4D7A861DDBB156001E085D /* BPExitStatus.m in Sources */, + FB5375202A66288300496432 /* BPTestInspectionHandler.m in Sources */, BA944BD01D76A39A00A4BDA3 /* Bluepill.m in Sources */, 7A564C0E1DA817DE001BCEC2 /* BPTreeObjects.m in Sources */, 7A7901861D5CB679004D4325 /* main.m in Sources */, @@ -838,11 +896,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + FB5375212A66288300496432 /* BPTestInspectionHandler.m in Sources */, C4D686182267A8C9007D4237 /* BPTestHelper.m in Sources */, C47B2DB3225813C70068C5CA /* BPWriter.m in Sources */, C4F08F75224C45750001AD2A /* BPExitStatus.m in Sources */, B3DAF83E2151CB9700210286 /* BPWaitTimer.m in Sources */, B3DAF83D2151CB7000210286 /* BPHandler.m in Sources */, + FBD772C22A3453010098CEFD /* BPTestUtils.m in Sources */, B3DAF83C2151CB4100210286 /* BPDeleteSimulatorHandler.m in Sources */, B3DAF83B2151CB3300210286 /* BPCreateSimulatorHandler.m in Sources */, BA9ADEB421657004001306F5 /* BPApplicationLaunchHandler.m in Sources */, @@ -863,6 +923,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + FBD772BF2A33E1DA0098CEFD /* BluepillUnhostedBatchingTests.m in Sources */, BA19493B1E4AF83E00881887 /* BPTMDControlConnection.m in Sources */, BAB24F711DB5DBED00867756 /* SimulatorHelperTests.m in Sources */, 7A4D7A811DDA5FA1001E085D /* BPTreeParserTests.m in Sources */, @@ -874,7 +935,7 @@ 7ACE1F721DD3D27D00C0FA73 /* WaitTimerTests.m in Sources */, BA180A091DBB00FA00D7D130 /* BPUtilsTests.m in Sources */, 7A4D7A871DDBB156001E085D /* BPExitStatus.m in Sources */, - BA1809B81DB89B5600D7D130 /* BluepillTests.m in Sources */, + BA1809B81DB89B5600D7D130 /* BluepillHostedTests.m in Sources */, BA1949371E4AF82F00881887 /* BPTMDRunnerConnection.m in Sources */, 7A7E7BC11DF22CE1007928F3 /* BPExecutionContext.m in Sources */, BAD558D71DB6DCB100C9A5CD /* BPWriter.m in Sources */, @@ -884,6 +945,9 @@ BAD558D51DB6DCB100C9A5CD /* BPTreeObjects.m in Sources */, BA0097001DCA61210000DD45 /* BPConfigurationTests.m in Sources */, BAB24F741DB5DFA200867756 /* BPTestHelper.m in Sources */, + FB1C695629A6E58500BFDFB4 /* BluepillUnhostedTests.m in Sources */, + FB21F0DE2A65103F00682AC7 /* BPTestCaseInfoTests.m in Sources */, + FBBBD8EF29A06AF6002B9115 /* BPTestUtils.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1031,6 +1095,10 @@ "$(SRCROOT)/src", ); INFOPLIST_FILE = "$(SRCROOT)/src/Info.plist"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); MACOSX_DEPLOYMENT_TARGET = 11.0; OTHER_CFLAGS = "-DBP_USE_PRIVATE_FRAMEWORKS"; OTHER_LDFLAGS = ( @@ -1070,6 +1138,10 @@ "$(SRCROOT)/src", ); INFOPLIST_FILE = "$(SRCROOT)/src/Info.plist"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); MACOSX_DEPLOYMENT_TARGET = 11.0; OTHER_CFLAGS = "-DBP_USE_PRIVATE_FRAMEWORKS"; OTHER_LDFLAGS = ( @@ -1115,6 +1187,10 @@ "$(SRCROOT)/src", ); INFOPLIST_FILE = "$(SRCROOT)/src/Info.plist"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); MACH_O_TYPE = staticlib; MACOSX_DEPLOYMENT_TARGET = 11.0; OTHER_LDFLAGS = ""; @@ -1152,6 +1228,10 @@ "$(SRCROOT)/src", ); INFOPLIST_FILE = "$(SRCROOT)/src/Info.plist"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); MACH_O_TYPE = staticlib; MACOSX_DEPLOYMENT_TARGET = 11.0; OTHER_LDFLAGS = ""; @@ -1175,6 +1255,10 @@ ); INFOPLIST_FILE = tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "\"$(DEVELOPER_DIR)/Library/PrivateFrameworks\""; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); MACOSX_DEPLOYMENT_TARGET = 11.0; OTHER_CFLAGS = "-DBP_USE_PRIVATE_FRAMEWORKS"; OTHER_LDFLAGS = ( @@ -1207,6 +1291,10 @@ ); INFOPLIST_FILE = tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "\"$(DEVELOPER_DIR)/Library/PrivateFrameworks\""; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); MACOSX_DEPLOYMENT_TARGET = 11.0; OTHER_CFLAGS = "-DBP_USE_PRIVATE_FRAMEWORKS"; OTHER_LDFLAGS = ( diff --git a/bp/bp.xcodeproj/xcshareddata/xcschemes/bp.xcscheme b/bp/bp.xcodeproj/xcshareddata/xcschemes/bp.xcscheme index 0ea5c1cc..f3be982d 100644 --- a/bp/bp.xcodeproj/xcshareddata/xcschemes/bp.xcscheme +++ b/bp/bp.xcodeproj/xcshareddata/xcschemes/bp.xcscheme @@ -20,6 +20,34 @@ ReferencedContainer = "container:bp.xcodeproj"> + + + + + + + + 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 literal 0 HcmV?d00001 diff --git a/bp/libBPTestInspector.dylib b/bp/libBPTestInspector.dylib new file mode 100755 index 0000000000000000000000000000000000000000..2ba6e426330eabf169464d62b0897c717e9f47a5 GIT binary patch literal 179504 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 z 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 literal 0 HcmV?d00001 diff --git a/bp/src/BPConfiguration.h b/bp/src/BPConfiguration.h index 3e845ab9..3d37af54 100644 --- a/bp/src/BPConfiguration.h +++ b/bp/src/BPConfiguration.h @@ -12,6 +12,7 @@ @class SimDeviceType; @class SimRuntime; +@class BPTestCaseInfo; /** TestPlans populated by an outside build system @@ -53,6 +54,7 @@ typedef NS_ENUM(NSInteger, BPProgram) { */ @property (nonatomic, strong) NSUUID *sessionIdentifier; +@property (nonatomic) BOOL isLogicTestTarget; @property (nonatomic, strong) NSString *appBundlePath; // XCUITest sector @@ -131,6 +133,9 @@ typedef NS_ENUM(NSInteger, BPProgram) { @property (nonatomic, strong) SimDeviceType *simDeviceType; @property (nonatomic, strong) SimRuntime *simRuntime; +@property (nonatomic, strong) NSDictionary *allTests; +@property (nonatomic, strong) NSString *xctestBinaryPath; +@property (nonatomic, strong) NSString *dyldFrameworkPath; /** Return a structure suitable for passing to `getopt()`'s long options. diff --git a/bp/src/BPConfiguration.m b/bp/src/BPConfiguration.m index 92c02ae8..f72b7899 100644 --- a/bp/src/BPConfiguration.m +++ b/bp/src/BPConfiguration.m @@ -150,6 +150,8 @@ typedef NS_OPTIONS(NSUInteger, BPOptionType) { "Directory where videos of test runs will be saved. If not provided, videos are not recorded."}, {368, "keep-passing-videos", BLUEPILL_BINARY | BP_BINARY, NO, NO, no_argument, "Off", BP_VALUE | BP_BOOL, "keepPassingVideos", "Whether recorded videos should be kept if the test passed. They are deleted by default."}, + {369, "logic-test", BLUEPILL_BINARY | BP_BINARY, NO, NO, no_argument, "Off", BP_VALUE | BP_BOOL, "isLogicTestTarget", + "Will run the tests without an app host"}, {0, 0, 0, 0, 0, 0, 0} }; @@ -168,9 +170,6 @@ - (BOOL)isValid:(NSError **)errPtr { if (!self.testBundlePath) { [errors addObject:@"testBundlePath field is nil"]; } - if (!self.testHost) { - [errors addObject:@"testHost field is nil"]; - } if ([errors count] > 0) { BP_SET_ERROR(errPtr, [NSString stringWithFormat:@"Invalid BPTestPlan object, %@.", @@ -614,7 +613,7 @@ - (BOOL)processOptionsWithError:(NSError **)errPtr { } // Now check we didn't miss any require options: NSMutableArray *errors = [[NSMutableArray alloc] init]; - if (!(self.appBundlePath) && !(self.xcTestRunPath) && !(self.testPlanPath) && !(self.deleteSimUDID)) { + if (!(self.appBundlePath) && !(self.xcTestRunPath) && !(self.testPlanPath) && !(self.deleteSimUDID) && !(self.isLogicTestTarget)) { [errors addObject:@"Missing required option: -a/--app OR --xctestrun-path OR --test-plan-path"]; } if ((self.program & BP_BINARY) && !(self.testBundlePath) && !(self.testPlanPath) && !(self.deleteSimUDID)) { @@ -707,7 +706,7 @@ - (BOOL)validateConfigWithError:(NSError *__autoreleasing *)errPtr { return NO; } - if (!self.appBundlePath && !self.xcTestRunDict && !self.tests) { + if (!self.appBundlePath && !self.xcTestRunDict && !self.tests && !self.isLogicTestTarget) { BP_SET_ERROR(errPtr, @"No app bundle provided."); return NO; } diff --git a/bp/src/BPHandler.h b/bp/src/BPHandler.h index 2d0ddbbc..d2a0d276 100644 --- a/bp/src/BPHandler.h +++ b/bp/src/BPHandler.h @@ -18,6 +18,8 @@ typedef void (^BasicHandlerBlock)(void); typedef void (^BasicErrorBlock)(NSError *error); +@property (nonatomic, assign, class) NSInteger timeoutErrorCode; + @property (nonatomic, strong) BPWaitTimer *timer; @property (nonatomic, copy) BasicHandlerBlock beginWith; diff --git a/bp/src/BPHandler.m b/bp/src/BPHandler.m index edeccb2b..8453c948 100644 --- a/bp/src/BPHandler.m +++ b/bp/src/BPHandler.m @@ -31,7 +31,7 @@ - (void)setup { } // call timeout block first and then execute the onError block if (__self.onError) { - NSError *error = [NSError errorWithDomain:BPErrorDomain code:-1 userInfo:nil]; + NSError *error = [NSError errorWithDomain:BPErrorDomain code:BPHandler.timeoutErrorCode userInfo:nil]; __self.onError(error); } }); @@ -63,4 +63,8 @@ - (BasicErrorBlock)defaultHandlerBlock { }; } ++ (NSInteger)timeoutErrorCode { + return 8; +} + @end diff --git a/bp/src/BPSimulator.h b/bp/src/BPSimulator.h index c1f652c9..5713fb60 100644 --- a/bp/src/BPSimulator.h +++ b/bp/src/BPSimulator.h @@ -15,6 +15,7 @@ @class BPConfiguration; @class BPTreeParser; @class SimDevice; +@class BPTestCaseInfo; @interface BPSimulator : NSObject @@ -42,6 +43,25 @@ - (void)launchApplicationAndExecuteTestsWithParser:(BPTreeParser *)parser andCompletion:(void (^)(NSError *, pid_t))completion; +/** + Launches a process which inspects a test bundle, and calls a completion block with a list of all tests in the bundle. + + @discussion The only way to do this is to actually launch a test execution with an injected Bluepill module. This module + then checks what test cases exist at runtime, and writes these cases to a file before ending the process (such that no tests are actually run). + We then read that file, and pass the results to the completion handler provided. + */ +- (void)collectTestSuiteInfoWithCompletion:(void (^)(NSArray *, NSError *))completionBlock; + +/** + Executes logic tests for the provided context, providing two callbacks as the execution starts and finishes. + @param parser The test log parser + @param spawnBlock Called once the process is spawned (right after it's started and the process is actually running) + @param completionBlock Called once the process completes, and all tests have either passed/failed/erred. + */ +- (void)executeLogicTestsWithParser:(BPTreeParser *)parser + onSpawn:(void (^)(NSError *, pid_t))spawnBlock + andCompletion:(void (^)(NSError *, pid_t))completionBlock; + - (void)deleteSimulatorWithCompletion:(void (^)(NSError *error, BOOL success))completion; - (void)addPhotosToSimulator; diff --git a/bp/src/BPSimulator.m b/bp/src/BPSimulator.m index b9367258..99e47d18 100644 --- a/bp/src/BPSimulator.m +++ b/bp/src/BPSimulator.m @@ -18,6 +18,8 @@ #import "BPWaitTimer.h" #import "PrivateHeaders/CoreSimulator/CoreSimulator.h" #import "SimulatorHelper.h" +#import +#import @interface BPSimulator() @@ -451,18 +453,235 @@ - (BOOL)uninstallApplicationWithError:(NSError *__autoreleasing *)errPtr { error:errPtr]; } -- (void)launchApplicationAndExecuteTestsWithParser:(BPTreeParser *)parser andCompletion:(void (^)(NSError *, pid_t))completion { - NSString *hostBundleId = [SimulatorHelper bundleIdForPath:self.config.appBundlePath]; - NSString *hostAppExecPath = [SimulatorHelper executablePathforPath:self.config.appBundlePath]; +/** + Logic tests are run directly on the simulator by spawning a new process with the XCTest executable, without any kind of host app. + */ +- (void)executeLogicTestsWithParser:(BPTreeParser *)parser + onSpawn:(void (^)(NSError *, pid_t))spawnBlock + andCompletion:(void (^)(NSError *, pid_t))completionBlock { + /* + Grab all test cases so that we can: + 1) create a timeout for the full test execution + 2) Support opting-out of tests, despite the fact that XCTest only provides an opt-in API. + */ + NSArray *testsToRun = [SimulatorHelper testsToRunWithConfig:self.config]; + NSString *testsToRunArg = testsToRun.count == self.config.allTestCases.count ? @"All" : [testsToRun componentsJoinedByString:@","]; + + /* + It can be useful to understand how to translate the public Breaking down CLI command `xcrun simctl spawn... `, which you'd + use to run a logic test from commandline, into the form that Bluepill must use, which is CoreSimulator's private `spawnWithPath`: + + When trying to run a command such as + ``` + xcrun simctl spawn -s AFF4165A-9A71-4860-8B6D-485B7D1BA2BC + /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Agents/xctest + -XCTest BPLogicTests/testPassingLogicTest /Users/.../BPLogicTests.xctest + ``` + we can break down the individual components to be handled in the following: + - part of the method signature: + - xcrun simctl // these just redirect down to CoreSimulator + - spawn // this is the the spawn method :p + - AFF4165A-9A71-4860-8B6D-485B7D1BA2BC // We're calling device.spawn. No need to respecify the device ID + - path: /Applications/.../Xcode/Agents/xctest + - options: + - -s // "standalone" option + - -w // "wait_for_debugger" option + - args: + - /Applications/.../Xcode/Agents/xctest // Yes, spawn redundantly requires this both the path param + an arg :) + - -XCTest + - BPLogicTests/testPassingLogicTest // The filter for which tests to actually run. `All` is the catch-all. + - /Users/.../BPLogicTests.xctest // The path to the .xctest file w/ all the module's tests. + + From `xcrun simctl spawn help`: + `simctl spawn [-w | --wait-for-debugger] [-s | --standalone] [-a | --arch=] [ ... ]` + */ + NSString *xctestPath = self.config.testBundlePath; + NSArray *arguments = @[ + self.config.xctestBinaryPath, + @"-XCTest", + testsToRunArg, + xctestPath, + ]; - // One bp instance only run one kind of xctest file. - if (self.config.testRunnerAppPath) { - hostAppExecPath = [SimulatorHelper executablePathforPath:self.config.testRunnerAppPath]; - hostBundleId = [SimulatorHelper bundleIdForPath:self.config.testRunnerAppPath]; + // Intercept stdout, stderr and post as simulator-output events + NSString *simStdoutPath = [SimulatorHelper makeStdoutFileOnDevice:self.device]; + NSString *simStdoutRelativePath = [simStdoutPath substringFromIndex:self.device.dataPath.length]; + // Environment + NSMutableDictionary *environment = [@{ + kOptionsStdoutKey: simStdoutRelativePath, + kOptionsStderrKey: simStdoutRelativePath, + } mutableCopy]; + if (self.config.dyldFrameworkPath) { + environment[@"DYLD_FRAMEWORK_PATH"] = self.config.dyldFrameworkPath; + environment[@"DYLD_LIBRARY_PATH"] = self.config.dyldFrameworkPath; + } + [environment addEntriesFromDictionary:self.config.environmentVariables]; + + self.appOutput = [NSFileHandle fileHandleForReadingAtPath:simStdoutPath]; + + NSFileHandle *outputFileHandle = [NSFileHandle fileHandleForWritingAtPath:simStdoutPath]; + NSNumber *stdoutFileDescriptor = @(outputFileHandle.fileDescriptor); + + NSDictionary *options = @{ + kOptionsArgumentsKey: arguments, + kOptionsEnvironmentKey: environment, + @"standalone": @(1), + @"stdout": stdoutFileDescriptor, + @"stderr": stdoutFileDescriptor, + }; + + // Set up monitor + if (!self.monitor) { + self.monitor = [[SimulatorMonitor alloc] initWithConfiguration:self.config]; } - // Create the environment for the host application - NSMutableDictionary *argsAndEnv = [[NSMutableDictionary alloc] init]; - NSArray *argumentsArr = self.config.commandLineArguments ?: @[]; + self.monitor.device = self.device; + parser.delegate = self.monitor; + + self.appOutput.readabilityHandler = ^(NSFileHandle *handle) { + // This callback occurs on a background thread + NSData *chunk = [handle availableData]; + [parser handleChunkData:chunk]; + }; + + + [BPUtils printInfo:DEBUGINFO withString:@"Spawning xctest execution with options: %@", options]; + + // To see more on how to debug the expected format/inputs of the options array, + // see the in-depth documentation in SimDevice.h. + __block typeof(self) blockSelf = self; + [self.device spawnAsyncWithPath:self.config.xctestBinaryPath + options:options + terminationHandler:^(int stat_loc) { + // The naming here is confusing, but this `terminationHandler` is called once + // the xctest process COMPLETES. The `completionHandler` below is used earlier, + // once the xctest process is SPAWNED. + + // Check the location where the status code is stored; + // Handle error if there is a signal or non-zero exit code. + NSError *error = [BPSimulator errorFromStatusLocation:stat_loc]; + if (error) { + [blockSelf signalCloseToParser:parser fileHandle:outputFileHandle]; + } + [blockSelf cleanUpParser:parser]; + completionBlock(error, blockSelf.monitor.appPID); + } completionHandler:^(NSError *error, pid_t pid) { + // Again, this `completionHandler` is called once the process is done SPAWNING, + // as opposed to happening after the process itself has finished. + blockSelf.monitor.appPID = pid; + blockSelf.monitor.appState = Running; + spawnBlock(error, pid); + }]; +} + +/** + Spawns a child xctest process (which we cause to not actually run tests), injecting the BPTestInspector + to pipe out test information instead. + */ +- (void)collectTestSuiteInfoWithCompletion:(void (^)(NSArray *, NSError *))completionBlock { + NSString *xctestPath = self.config.testBundlePath; + NSArray *arguments = @[ + self.config.xctestBinaryPath, + @"-XCTest", + @"All", + xctestPath, + ]; + + NSString *testSuiteInfoOutputPath = [SimulatorHelper makeTestWrapperOutputFileOnDevice:self.device]; + // Intercept stdout, stderr and post as simulator-output events + NSString *simStdoutPath = [SimulatorHelper makeStdoutFileOnDevice:self.device]; + NSString *simStdoutRelativePath = [simStdoutPath substringFromIndex:self.device.dataPath.length]; + + // Environment + NSMutableDictionary *environment = [[SimulatorHelper logicTestEnvironmentWithConfig:self.config stdoutRelativePath:simStdoutRelativePath] mutableCopy]; + + + NSString *testInspectorPath = [BPUtils findBPTestInspectorDYLIB]; + [BPUtils printInfo:DEBUGINFO withString: @"Injecting libBPTestInspector.dylib at path: %@", testInspectorPath]; + + environment[@"DYLD_INSERT_LIBRARIES"] = testInspectorPath; + environment[BPTestInspectorConstants.outputPathEnvironmentKey] = testSuiteInfoOutputPath; + environment[BPTestInspectorConstants.testBundleEnvironmentKey] = xctestPath; + + NSFileHandle *outputFileHandle = [NSFileHandle fileHandleForWritingAtPath:simStdoutPath]; + NSNumber *stdoutFileDescriptor = @(outputFileHandle.fileDescriptor); + + NSDictionary *options = @{ + kOptionsArgumentsKey: arguments, + kOptionsEnvironmentKey: environment, + @"standalone": @(1), + }; + + // To see more on how to debug the expected format/inputs of the options array, + // see the in-depth documentation in SimDevice.h. + __block typeof(self) blockSelf = self; + [self.device spawnAsyncWithPath:self.config.xctestBinaryPath + options:options + terminationHandler:^(int stat_loc) { + NSError *error = [BPSimulator errorFromStatusLocation:stat_loc]; + if (error) { + completionBlock(nil, error); + return; + } + // Retrieve test data + [BPUtils printInfo:INFO withString: @"Test inspector execution completed successfully"]; + + NSError *unarchiveError; + + NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:testSuiteInfoOutputPath]; + NSData *testData = [fileHandle readDataToEndOfFile]; + NSArray *testBundleInfo = [NSKeyedUnarchiver unarchivedArrayOfObjectsOfClass:BPTestCaseInfo.class + fromData:testData + error:&unarchiveError]; + [fileHandle closeFile]; + // Cleanup + Completion + [NSFileManager.defaultManager removeItemAtPath:testSuiteInfoOutputPath error:nil]; + if (unarchiveError) { + [BPUtils printInfo:ERROR withString: @"BPTestInspector failed with unarchiver error: %@", unarchiveError.userInfo]; + } else { + [BPUtils printInfo:INFO withString: @"BPTestInspector returned list of tests of size: %@", @(testBundleInfo.count)]; + } + completionBlock(testBundleInfo, unarchiveError); + } completionHandler:^(NSError *error, pid_t pid) { + if (error) { + completionBlock(nil, error); + } + }]; +} + ++ (NSError *)errorFromStatusLocation:(int)stat_loc { + if (WIFSIGNALED(stat_loc)) { + int signalCode = WTERMSIG(stat_loc); + // Ignore if the process was killed -- this occurs when we're killing + // a timed-out test, and shouldn't be treated as a crash. + if (signalCode != SIGKILL) { + [BPUtils printInfo:WARNING withString: @"Spawned XCTest execution failed with signal code: %@", @(signalCode)]; + return [BPUtils errorWithSignalCode:signalCode]; + } + } else { + // A non-zero exit code could mean a failed test or something more serious, but we can't tell the difference here. + // The best we can do is log the error code as debug info. + int exitCode = WEXITSTATUS(stat_loc); + if (exitCode) { + [BPUtils printInfo:INFO withString: @"Spawned XCTest execution failed with error code: %@", @(exitCode)]; + } + } + return nil; +} + +// Posts a APPCLOSED signal to the parser, indicating a crash/kill +- (void)signalCloseToParser:(BPTreeParser *)parser fileHandle:(NSFileHandle *)fileHandle { + [fileHandle seekToEndOfFile]; + [fileHandle writeData:[@"\nBP_APP_PROC_ENDED\n" dataUsingEncoding:NSUTF8StringEncoding]]; + [fileHandle closeFile]; +} + +- (void)cleanUpParser:(BPTreeParser *)parser { + self.monitor.appState = Completed; + [parser.delegate setParserStateCompleted]; +} + ++ (NSMutableArray *)commandLineArgsFromConfig:(BPConfiguration *)config { + NSArray *argumentsArr = config.commandLineArguments ?: @[]; NSMutableArray *commandLineArgs = [NSMutableArray array]; for (NSString *argument in argumentsArr) { NSArray *argumentsArray = [argument componentsSeparatedByString:@" "]; @@ -472,6 +691,21 @@ - (void)launchApplicationAndExecuteTestsWithParser:(BPTreeParser *)parser andCom } } } + return commandLineArgs; +} + +- (void)launchApplicationAndExecuteTestsWithParser:(BPTreeParser *)parser andCompletion:(void (^)(NSError *, pid_t))completion { + NSString *hostBundleId = [SimulatorHelper bundleIdForPath:self.config.appBundlePath]; + NSString *hostAppExecPath = [SimulatorHelper executablePathforPath:self.config.appBundlePath]; + + // One bp instance only run one kind of xctest file. + if (self.config.testRunnerAppPath) { + hostAppExecPath = [SimulatorHelper executablePathforPath:self.config.testRunnerAppPath]; + hostBundleId = [SimulatorHelper bundleIdForPath:self.config.testRunnerAppPath]; + } + // Create the environment for the host application + NSMutableArray *commandLineArgs = [BPSimulator commandLineArgsFromConfig:self.config]; + NSMutableDictionary *argsAndEnv = [[NSMutableDictionary alloc] init]; // These are appended by Xcode so we do that here. [commandLineArgs addObjectsFromArray:@[ @@ -548,13 +782,8 @@ - (void)launchApplicationAndExecuteTestsWithParser:(BPTreeParser *)parser andCom dispatch_source_cancel(source); }); dispatch_source_set_cancel_handler(source, ^{ - blockSelf.monitor.appState = Completed; - [parser.delegate setParserStateCompleted]; - // Post a APPCLOSED signal to the parser - NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:simStdoutPath]; - [fileHandle seekToEndOfFile]; - [fileHandle writeData:[@"\nBP_APP_PROC_ENDED\n" dataUsingEncoding:NSUTF8StringEncoding]]; - [fileHandle closeFile]; + [blockSelf signalCloseToParser:parser fileHandle:[NSFileHandle fileHandleForWritingAtPath:simStdoutPath]]; + [blockSelf cleanUpParser:parser]; }); dispatch_resume(source); self.appOutput.readabilityHandler = ^(NSFileHandle *handle) { diff --git a/bp/src/BPStats.h b/bp/src/BPStats.h index 90021d05..34dc9a51 100644 --- a/bp/src/BPStats.h +++ b/bp/src/BPStats.h @@ -16,6 +16,9 @@ #define INSTALL_APPLICATION(x) [NSString stringWithFormat:@"[Attempt %lu] Install Application", (x)] #define UNINSTALL_APPLICATION(x) [NSString stringWithFormat:@"[Attempt %lu] Uninstall Application", (x)] #define LAUNCH_APPLICATION(x) [NSString stringWithFormat:@"[Attempt %lu] Launch Application", (x)] +#define TEST_INSPECTION(x) [NSString stringWithFormat:@"[Attempt %lu] Inspect Tests", (x)] +#define SPAWN_LOGIC_TEST(x) [NSString stringWithFormat:@"[Attempt %lu] Spawn Logic Tests", (x)] +#define EXECUTE_LOGIC_TEST(x) [NSString stringWithFormat:@"[Attempt %lu] Execute Logic Tests", (x)] #define DELETE_SIMULATOR(x) [NSString stringWithFormat:@"[Attempt %lu] Delete Simulator", (x)] #define DELETE_SIMULATOR_CB(x) [NSString stringWithFormat:@"[Attempt %lu] Delete Simulator due to BAD STATE", (x)] diff --git a/bp/src/BPTestInspectionHandler.h b/bp/src/BPTestInspectionHandler.h new file mode 100644 index 00000000..735a7742 --- /dev/null +++ b/bp/src/BPTestInspectionHandler.h @@ -0,0 +1,27 @@ +// Copyright 2016 LinkedIn Corporation +// Licensed under the BSD 2-Clause License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://opensource.org/licenses/BSD-2-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + + +#import +#import + +@class BPTestCaseInfo; + +typedef void (^TestInspectionBlock)(NSArray *testBundleInfo, NSError *error); +typedef void (^TestInspectionSuccessBlock)(NSArray *testBundleInfo); + +@interface BPTestInspectionHandler : BPHandler + +@property (nonatomic, copy) TestInspectionSuccessBlock onSuccess; + +- (TestInspectionBlock)defaultHandlerBlock; + + + +@end diff --git a/bp/src/BPTestInspectionHandler.m b/bp/src/BPTestInspectionHandler.m new file mode 100644 index 00000000..0dec1708 --- /dev/null +++ b/bp/src/BPTestInspectionHandler.m @@ -0,0 +1,44 @@ +// Copyright 2016 LinkedIn Corporation +// Licensed under the BSD 2-Clause License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://opensource.org/licenses/BSD-2-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +#import "BPTestInspectionHandler.h" + +#import + +@implementation BPTestInspectionHandler + +@dynamic onSuccess; + +- (TestInspectionBlock)defaultHandlerBlock { + return ^(NSArray *testBundleInfo, NSError *error) { + dispatch_once(&self->onceToken, ^{ + [self.timer cancelTimer]; + + self.error = error; + + if (self.beginWith) { + self.beginWith(); + } + + if (error) { + if (self.onError) { + self.onError(error); + } + } else if (self.onSuccess) { + self.onSuccess(testBundleInfo); + } + + if (self.endWith) { + self.endWith(); + } + }); + }; +} + +@end diff --git a/bp/src/BPUtils.h b/bp/src/BPUtils.h index 209f3428..a1defcdf 100644 --- a/bp/src/BPUtils.h +++ b/bp/src/BPUtils.h @@ -29,6 +29,7 @@ typedef NS_ENUM(int, BPKind) { }; @class BPConfiguration; +@class BPExecutionContext; @interface BPUtils : NSObject @@ -50,6 +51,8 @@ typedef NS_ENUM(int, BPKind) { */ + (BOOL)isBuildScript; ++ (NSString *)findBPTestInspectorDYLIB; + + (NSString *)findExecutablePath:(NSString *)execName; /*! @@ -71,7 +74,6 @@ typedef NS_ENUM(int, BPKind) { */ + (NSString *)mkstemp:(NSString *)pathTemplate withError:(NSError **)errPtr; - /*! @discussion print a message to stdout. @param kind one of the levels in BPKind @@ -80,8 +82,20 @@ typedef NS_ENUM(int, BPKind) { + (void)printInfo:(BPKind)kind withString:(NSString *)fmt, ... NS_FORMAT_FUNCTION(2,3); /*! - @discussion get an NSError * - This is not really meant to be called, use the BP_SET_ERROR macro below instead. + Creates an `NSError *` with BP-specific domain for a given signal code, updating the description accordingly. + @param signalCode The signal code. + */ ++ (NSError *)errorWithSignalCode:(NSInteger)signalCode; + +/*! + Creates an `NSError *` with BP-specific domain for a given exit code, updating the description accordingly. + @param exitCode The exit code. + */ ++ (NSError *)errorWithExitCode:(NSInteger)exitCode; + +/*! + @discussion get an `NSError *` + This is not really meant to be called, use the `BP_SET_ERROR` macro below instead. @param function The name of the function @param line The line number @param fmt a format string (a la printf), followed by var args. @@ -111,6 +125,18 @@ typedef NS_ENUM(int, BPKind) { + (BPConfiguration *)normalizeConfiguration:(BPConfiguration *)config withTestFiles:(NSArray *)xctTestFiles; +/*! + Returns an aggregated timeout interval for all tests to be run in an execution. While we will + still apply a per-test timeout, it's possible for things to fail in an XCTest execution when a test isn't + being run, and we want to make sure the execution still fails when this occurs. + + @discussion This timeout value is based on the timeout per test multiplied by the number of tests, + with an additional buffer per test. + @param config The fully setup configuration that will be used to calculate the aggregate timeout. + @return The aggregated timeout. + */ ++ (double)timeoutForAllTestsWithConfiguration:(BPConfiguration *)config; + /*! @discussion a function to determine if the given file name represents stdout. A file name is considered stdout if it is '-' or 'stdout'. @@ -226,4 +252,24 @@ typedef BOOL (^BPRunBlock)(void); testTimes:(NSDictionary *)testTimes andXCTestFiles:(NSArray *)xcTestFiles; +#pragma mark - Logic Test Architecture Helpers + +/** + We can isolate a single architecture out of a universal binary using the `lipo -extract` command. By doing so, we can + force an executable (such as XCTest) to always run w/ the architecture we expect. This is to avoid some funny business where + the architecture selected can be unexpected depending on multiple factors, such as Rosetta, xcode version, etc. + + @return the path of the new executable if possible + required, nil otherwise. In nil case, original executable should be used instead. + */ ++ (NSString *)lipoExecutableAtPath:(NSString *)path withContext:(BPExecutionContext *)context; + +/** + Lipo'ing the universal binary alone to isolate the desired architecture will result in errors. + Specifically, the newly lipo'ed binary won't be able to find any of the required frameworks + from within the original binary. So, we need to set up the `DYLD_FRAMEWORK_PATH` + in the environment to include the paths to these frameworks within the original universal + executable's binary. + */ ++ (NSString *)correctedDYLDFrameworkPathFromBinary:(NSString *)binaryPath; + @end diff --git a/bp/src/BPUtils.m b/bp/src/BPUtils.m index 3b8bf87b..e9541ab6 100644 --- a/bp/src/BPUtils.m +++ b/bp/src/BPUtils.m @@ -12,6 +12,11 @@ #import "BPConstants.h" #import "BPXCTestFile.h" #import "BPConfiguration.h" +#import "SimDevice.h" +#import "SimDeviceType.h" +#import "BPExecutionContext.h" +#import "BPSimulator.h" +#import @implementation BPUtils @@ -113,16 +118,6 @@ + (void)printTo:(FILE*)fd kind:(BPKind)kind withString:(NSString *)txt { fflush(fd); } -+ (NSError *)BPError:(const char *)function andLine:(int)line withFormat:(NSString *)fmt, ... { - va_list args; - va_start(args, fmt); - NSString *msg = [[NSString alloc] initWithFormat:fmt arguments:args]; - va_end(args); - return [NSError errorWithDomain:BPErrorDomain - code:-1 - userInfo:@{NSLocalizedDescriptionKey: msg}]; -} - + (NSString *)findExecutablePath:(NSString *)execName { NSString *argv0 = [[[NSProcessInfo processInfo] arguments] objectAtIndex:0]; NSString *execPath = [[argv0 stringByDeletingLastPathComponent] stringByAppendingPathComponent:execName]; @@ -132,6 +127,44 @@ + (NSString *)findExecutablePath:(NSString *)execName { return execPath; } ++ (NSString *)findBPTestInspectorDYLIB { + NSString *argv0 = [[[NSProcessInfo processInfo] arguments] objectAtIndex:0]; + // While the Bluepill binary is in a 'Release' or 'Debug' directory, the + // simulator build for the libBPTestInspector.dylib file is in a sibling + // directory 'Release-iphonesimulator' or 'Debug-iphonesimulator' + NSString *iPhoneSimDir = [[argv0 stringByDeletingLastPathComponent] stringByAppendingString:@"-iphonesimulator"]; + NSString *path = [iPhoneSimDir stringByAppendingPathComponent:BPTestInspectorConstants.dylibName]; + + [BPUtils printInfo:INFO withString: @"Searching for libBPTestInspector.dylib at path: %@", path]; + if ([[NSFileManager defaultManager] isReadableFileAtPath:path]) { + return path; + } + + // The executable may also be in derived data, accessible from the app's current working directory. + NSString *buildProductsDir = [NSFileManager.defaultManager.currentDirectoryPath stringByDeletingLastPathComponent]; + iPhoneSimDir = [buildProductsDir stringByAppendingPathComponent:@"Debug-iphonesimulator"]; + path = [iPhoneSimDir stringByAppendingPathComponent:BPTestInspectorConstants.dylibName]; + + [BPUtils printInfo:INFO withString: @"Previous search failed. Now searching for libBPTestInspector.dylib at path: %@", path]; + if ([[NSFileManager defaultManager] isReadableFileAtPath:path]) { + return path; + } + + // The executable may also be in derived data, accessible from the app's current working directory. + buildProductsDir = [NSFileManager.defaultManager.currentDirectoryPath stringByDeletingLastPathComponent]; + iPhoneSimDir = [buildProductsDir stringByAppendingPathComponent:@"Release-iphonesimulator"]; + path = [iPhoneSimDir stringByAppendingPathComponent:BPTestInspectorConstants.dylibName]; + + [BPUtils printInfo:INFO withString: @"Previous search failed. Now searching for libBPTestInspector.dylib at path: %@", path]; + + if ([[NSFileManager defaultManager] isReadableFileAtPath:path]) { + return path; + } + + [BPUtils printInfo:ERROR withString: @"Unable to find libBPTestInspector.dylib to inject into xctest process and get test data"]; + return nil; +} + + (NSString *)mkdtemp:(NSString *)template withError:(NSError **)errPtr { char *dir = strdup([[template stringByAppendingString:@"_XXXXXX"] UTF8String]); if (mkdtemp(dir) == NULL) { @@ -418,6 +451,13 @@ + (NSDictionary *)loadSimpleJsonFile:(NSString *)filePath return testsToRunByFilePath; } ++ (double)timeoutForAllTestsWithConfiguration:(BPConfiguration *)config { + // Add 1 second per test + double buffer = 1.0; + NSInteger testCount = (config.testCasesToRun.count == 0 ? config.allTestCases.count : config.testCasesToRun.count) - config.testCasesToSkip.count; + return testCount * (config.testCaseTimeout.doubleValue + buffer); +} + + (double)getTotalTimeWithConfig:(BPConfiguration *)config testTimes:(NSDictionary *)testTimes andXCTestFiles:(NSArray *)xcTestFiles { @@ -438,4 +478,202 @@ + (double)getTotalTimeWithConfig:(BPConfiguration *)config return totalTime; } +#pragma mark - Errors + ++ (NSError *)errorWithSignalCode:(NSInteger)signalCode { + NSString *description = [NSString stringWithFormat:@"Process failed signal code: %@", @(signalCode)]; + return [self errorWithCode:signalCode description:description]; +} + ++ (NSError *)errorWithExitCode:(NSInteger)exitCode { + NSString *description = [NSString stringWithFormat:@"Process failed exit code: %@", @(exitCode)]; + return [self errorWithCode:exitCode description:description]; +} + ++ (NSError *)BPError:(const char *)function andLine:(int)line withFormat:(NSString *)fmt, ... { + va_list args; + va_start(args, fmt); + NSString *msg = [[NSString alloc] initWithFormat:fmt arguments:args]; + va_end(args); + return [self errorWithCode:-1 description:msg]; +} + ++ (NSError *)errorWithCode:(NSInteger)code description:(NSString *)description { + NSDictionary *userInfo = @{ + NSLocalizedDescriptionKey: description + }; + return [NSError errorWithDomain:BPErrorDomain code:code userInfo:userInfo]; +} + +#pragma mark - Architecture Helpers + +/** + We can isolate a single architecture out of a universal binary using the `lipo -extract` command. By doing so, we can + force an executable (such as XCTest) to always run w/ the architecture we expect. This is to avoid some funny business where + the architecture selected can be unexpected depending on multiple factors, such as Rosetta, xcode version, etc. + + @return the path of the new executable if possible + required, nil otherwise. In nil case, original executable should be used instead. + */ ++ (NSString *)lipoExecutableAtPath:(NSString *)path withContext:(BPExecutionContext *)context { + [BPUtils printInfo:INFO withString:@"Preparing to extract xctest executable into required architecture."]; + // If the executable isn't a universal binary, there's nothing we can do. If we don't + // support the test bundle type, we'll let it fail later naturally. + NSArray *executableArchitectures = [self availableArchitecturesForPath:path]; + BOOL isUniversalExecutable = [executableArchitectures containsObject:self.x86_64] && [executableArchitectures containsObject:self.arm64]; + if (!isUniversalExecutable) { + [BPUtils printInfo:INFO withString:@"xctest executable was not a universal binary. No extraction possible."]; + return nil; + } + // Now, get the test bundle's architecture. + NSString *bundlePath = context.config.testBundlePath; + NSString *testBundleName = [[bundlePath pathComponents].lastObject componentsSeparatedByString:@"."][0]; + NSString *testBundleBinaryPath = [bundlePath stringByAppendingPathComponent:testBundleName]; + NSArray *testBundleArchitectures = [self availableArchitecturesForPath:testBundleBinaryPath]; + BOOL isUniversalTestBundle = [testBundleArchitectures containsObject:self.x86_64] && [testBundleArchitectures containsObject:self.arm64]; + + // If the test bundle is a univeral binary, no need to lipo... xctest (regardless of the arch it's in) + // should be able to handle it. + if (isUniversalTestBundle) { + [BPUtils printInfo:INFO withString:@"Test bundle is a universal binary -- no architecture extraction required."]; + return nil; + } + + // If the test bundle's arch isn't supported by the sim, we're in an error state + NSArray *simArchitectures = [self architecturesSupportedByDevice:context.runner.device]; + if (![simArchitectures containsObject:testBundleArchitectures.firstObject]) { + [BPUtils printInfo:ERROR withString:@"The simulator being run does not support the test bundle's arch (%@)", testBundleArchitectures.firstObject]; + return nil; + } + + // Now that we've done any error checking, we can handle our real cases, + // based on what xctest would default to vs. what we need it to do. + // Note that the universal binary will launch in the same arch as the machine, + // rather than defaulting to the arch of the parent process. + // + // 1) The current arch is x86_64 + // a) We are in Rosetta -> xctest will default to arm64 + // b) We are not in Rosetta -> xctest will default to x86 + // 2) The current arch is arm64 -> xctest will default to arm64 + // + // We handle these accordingly: + // 1a) we lipo if the test bundle is an x86_64 binary + // 1b) no-op. ... x86 will get handled automatically, and we have to fail if test bundle is arm64. + // 2) no-op. ... arm64 will get handled automatically, and we have to fail if test bundle is x86. + BOOL isRosetta = [self.currentArchitecture isEqual:self.x86_64] && isUniversalExecutable; + [BPUtils printInfo:DEBUGINFO withString:@"lipo: currentArchitecture = %@", self.currentArchitecture]; + [BPUtils printInfo:DEBUGINFO withString:@"lipo: isUniversalExecutable = %@", @(isUniversalExecutable)]; + [BPUtils printInfo:DEBUGINFO withString:@"lipo: testBundleArchitectures = %@", testBundleArchitectures]; + [BPUtils printInfo:DEBUGINFO withString:@"lipo: isRosetta = %@", @(isRosetta)]; + if (!isRosetta || ![testBundleArchitectures.firstObject isEqual:self.x86_64]) { + [BPUtils printInfo:INFO withString:@"The test bundle matches the expected xctest architecture, so no arch extraction is required"]; + return nil; + } + + // Now we lipo. + [BPUtils printInfo:INFO withString:@"We are in Rosetta, but xctest will default to arm64. Extracting xctest x86_64 binary"]; + NSError *error; + NSString *fileName = [NSString stringWithFormat:@"%@xctest", NSTemporaryDirectory()]; + NSString *thinnedExecutablePath = [BPUtils mkstemp:fileName withError:&error]; + NSString *cmd = [NSString stringWithFormat:@"/usr/bin/lipo %@ -extract %@ -output %@", path, testBundleArchitectures.firstObject, thinnedExecutablePath]; + NSString *__unused output = [BPUtils runShell:cmd]; + return thinnedExecutablePath; +} + +/** + Lipo'ing the universal binary alone to isolate the desired architecture will result in errors. + Specifically, the newly lipo'ed binary won't be able to find any of the required frameworks + from within the original binary. So, we need to set up the `DYLD_FRAMEWORK_PATH` + in the environment to include the paths to these frameworks within the original universal + executable's binary. + */ ++ (NSString *)correctedDYLDFrameworkPathFromBinary:(NSString *)binaryPath { + NSString *otoolCommand = [NSString stringWithFormat:@"/usr/bin/otool -l %@", binaryPath]; + NSString *otoolInfo = [BPUtils runShell:otoolCommand]; + + /** + Example command: + `/usr/bin/otool -l /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Agents/xctest` + + Example output looks something like this: + + ``` + // Lots of stuff we don't care about, followed by a list of `LC_RPATH` entries + Load command 18 + cmd LC_RPATH + cmdsize 48 + path @executable_path/path/to/frameworks/ (offset 12) + // Lots more stuff we don't care about... + ``` + + We want to use a regex to extract out each of the `@executable_path/path/to/frameworks/`, + and then replace `@executable_path` with our original executable's parent directory. + */ + NSString *pattern = @"(?:^Load command \\d+\n" + "\\s*cmd LC_RPATH\n" + "\\s*cmdsize \\d+\n" + "\\s*path (@executable_path\\/.*) \\(offset \\d+\\))+"; + NSError *error; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern + options:NSRegularExpressionAnchorsMatchLines + error:&error]; + + NSMutableArray *paths = [NSMutableArray array]; + NSString *parentDirectory = [[binaryPath stringByResolvingSymlinksInPath] stringByDeletingLastPathComponent]; + if (regex) { + NSArray *matches = [regex matchesInString:otoolInfo + options:0 + range:NSMakeRange(0, otoolInfo.length)]; + for (NSTextCheckingResult *match in matches) { + // Extract the substring from the input string based on the matched range + NSString *relativePath = [otoolInfo substringWithRange:[match rangeAtIndex:1]]; + NSString *path = [relativePath stringByReplacingOccurrencesOfString:@"@executable_path" withString:parentDirectory]; + [paths addObject:path]; + } + } else { + [BPUtils printInfo:ERROR withString:@"Error creating regular expression: %@", error]; + } + return [paths componentsJoinedByString:@":"]; +} + ++ (NSArray *)availableArchitecturesForPath:(NSString *)path { + NSString *cmd = [NSString stringWithFormat:@"/usr/bin/lipo -archs %@", path]; + return [[BPUtils runShell:cmd] componentsSeparatedByString:@" "]; +} + ++ (NSArray *)architecturesSupportedByDevice:(SimDevice *)device { + NSArray *simSupportedArchitectures = device.deviceType.supportedArchs; + NSMutableArray *simArchitectures = [NSMutableArray array]; + for (NSNumber *supportedArchitecture in simSupportedArchitectures) { + [simArchitectures addObject:[self architectureName:supportedArchitecture.intValue]]; + } + return [simArchitectures copy]; +} + ++ (NSString *)arm64 { + return [self architectureName:CPU_TYPE_ARM64]; +} + ++ (NSString *)x86_64 { + return [self architectureName:CPU_TYPE_X86_64]; +} + ++ (NSString *)architectureName:(integer_t)architecture { + if (architecture == CPU_TYPE_X86_64) { + return @"x86_64"; + } else if (architecture == CPU_TYPE_ARM64) { + return @"arm64"; + } + return nil; +} + ++ (NSString *)currentArchitecture { + #if TARGET_CPU_ARM64 + return [self architectureName:CPU_TYPE_ARM64]; + #elif TARGET_CPU_X86_64 + return [self architectureName:CPU_TYPE_X86_64]; + #else + return nil; + #endif +} + @end diff --git a/bp/src/Bluepill.m b/bp/src/Bluepill.m index 82a471ad..cca344e0 100644 --- a/bp/src/Bluepill.m +++ b/bp/src/Bluepill.m @@ -21,8 +21,10 @@ #import #import "BPTMDControlConnection.h" #import "BPTMDRunnerConnection.h" +#import "BPTestInspectionHandler.h" #import "BPXCTestFile.h" #import +#import // CoreSimulator #import "PrivateHeaders/CoreSimulator/SimDevice.h" @@ -220,6 +222,16 @@ - (void)createContext { NSAssert(xctTestFile != nil, @"Failed to load testcases from: %@; Error: %@", context.config.testBundlePath, [error localizedDescription]); context.config.allTestCases = [[NSArray alloc] initWithArray: xctTestFile.allTestCases]; + if (context.config.isLogicTestTarget) { + // For estimating how long this will take (and setting an appropriate timeout), we need to + // remove any skipped tests that aren't actually a part of this bundle. + NSMutableSet *allTests = [NSMutableSet setWithArray:context.config.allTestCases]; + NSMutableSet *allSkippedTests = [NSMutableSet setWithArray:self.config.testCasesToSkip]; + [allSkippedTests intersectSet:allTests]; + context.config.testCasesToSkip = allSkippedTests.allObjects; + } + + context.attemptNumber = self.retries + 1; self.context = context; // Store the context on self so that it's accessible to the interrupt handler in the loop } @@ -256,7 +268,7 @@ - (void)setupExecutionWithContext:(BPExecutionContext *)context { // Set up retry counts. self.maxCreateTries = [self.config.maxCreateTries integerValue]; self.maxInstallTries = [self.config.maxInstallTries integerValue]; - + if (context.config.deleteSimUDID) { NEXT([self deleteSimulatorOnlyTaskWithContext:context]); } else { @@ -299,13 +311,8 @@ - (void)createSimulatorWithContext:(BPExecutionContext *)context { if (self.config.scriptFilePath) { [context.runner runScriptFile:self.config.scriptFilePath]; } - if (self.config.cloneSimulator) { - // launch application directly when clone simulator - NEXT([__self launchApplicationWithContext:context]); - } else { - // Install application when test without clone - NEXT([__self installApplicationWithContext:context]); - }; + + NEXT([__self enumerateTestsWithContext:context]); }; handler.onError = ^(NSError *error) { @@ -393,6 +400,178 @@ - (void)uninstallApplicationWithContext:(BPExecutionContext *)context { } } +/** + On Apple Silicon machines, the xctest executable is likely to be a universal binary. Strangely, however, this can cause issues when + Xcode is being run in Rosetta mode with an `x86_64` test bundle. While it would seem those would be compatible, the simulator + won't naturally respect that it was launched from a Rosetta process, and will try to boot using the `arm64` binary by default. + As a result, in such a scenario we need to do some work on our side to get a modified xctest binary that will boot using x86. + */ +- (void)adaptXCTestExecutableIfRequiredWithContext:(BPExecutionContext *)context { + // First we need to make sure the archs are consistent between the xctest executable + the test bundle. + NSString *originalXCTestPath = [[NSString alloc] initWithFormat: + @"%@/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Agents/xctest", + context.config.xcodePath]; + NSString *newXCTestPath = [BPUtils lipoExecutableAtPath:originalXCTestPath withContext:context]; + if (newXCTestPath) { + context.config.dyldFrameworkPath = [BPUtils correctedDYLDFrameworkPathFromBinary:originalXCTestPath]; + } + context.config.xctestBinaryPath = newXCTestPath ?: originalXCTestPath; +} + +/** + Will spawn a child xctest execution with an injected dylib to pipe out information about all the tests in a test bundle. + No tests will be run. + */ +- (void)enumerateTestsWithContext:(BPExecutionContext *)context { + NSString *stepName = TEST_INSPECTION(context.attemptNumber); + [BPUtils printInfo:INFO withString:@"%@", stepName]; + + // Now create handler for when the process completes + + [[BPStats sharedStats] startTimer:stepName]; + BPWaitTimer *timer = [BPWaitTimer timerWithInterval:[context.config.launchTimeout doubleValue]]; + [timer start]; + + // Set up completion handler for when we load the tests. + BPTestInspectionHandler *completionHandler = [BPTestInspectionHandler handlerWithTimer:timer]; + __weak typeof(self) __self = self; + completionHandler.onSuccess = ^(NSArray *testBundleInfo) { + // Note, we can't actually handle the tests themselves here... that will + // need to be handled in a wrapping block below. + [BPUtils printInfo:DEBUGINFO withString:@"Test bundle inspected for tests."]; + [[BPStats sharedStats] endTimer:stepName withResult:@"INFO"]; + + // Save the test info. + if (testBundleInfo) { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + for (BPTestCaseInfo *info in testBundleInfo) { + dictionary[info.prettifiedFullName] = info; + } + __self.config.allTests = [dictionary copy]; + context.config.allTests = [dictionary copy]; + } + + if (self.config.isLogicTestTarget) { + NEXT([__self executeLogicTestsWithContext:context]) + } else if (self.config.cloneSimulator) { + // launch application directly when clone simulator + NEXT([__self launchApplicationWithContext:context]); + } else { + // Install application when test without clone + NEXT([__self installApplicationWithContext:context]); + }; + }; + + completionHandler.onError = ^(NSError *error) { + [[BPStats sharedStats] endTimer:stepName withResult:@"ERROR"]; + [BPUtils printInfo:ERROR withString:@"Spawned logic test execution failed: %@", [error localizedDescription]]; + + BPExitStatus exitStatus = BPExitStatusLaunchAppFailed; + NEXT([__self deleteSimulatorWithContext:context andStatus:exitStatus]); + }; + + completionHandler.onTimeout = ^{ + [[BPStats sharedStats] endTimer:stepName withResult:@"TIMEOUT"]; + [BPUtils printInfo:FAILED withString:@"Timeout: %@", stepName]; + }; + + // If test cases are already enumerated, skip straight to executing tests. + if (context.config.allTests) { + completionHandler.defaultHandlerBlock(context.config.allTests.allValues, nil); + return; + } + + // Otherwise, make sure XCTest binary is formatted correctly for current arch. Then get test info. + [self adaptXCTestExecutableIfRequiredWithContext:context]; + [context.runner collectTestSuiteInfoWithCompletion:^(NSArray *testBundleInfo, NSError *error) { + completionHandler.defaultHandlerBlock(testBundleInfo, error); + }]; +} + +- (void)executeLogicTestsWithContext:(BPExecutionContext *)context { + [self adaptXCTestExecutableIfRequiredWithContext:context]; + + // We get two callbacks after trying to spawn an execution. They happen when: + // 1) Immediately after the process is spawned. + // 2) Once the process completes. + // This method sets up those two handlers, and then passes them to the runner. + + // 1) Handle process spawn + NSString *spawnStepName = SPAWN_LOGIC_TEST(context.attemptNumber); + NSString *executeTestsStepName = EXECUTE_LOGIC_TEST(context.attemptNumber); + [BPUtils printInfo:INFO withString:@"%@", spawnStepName]; + + // Now create handler for when the process completes + + [[BPStats sharedStats] startTimer:spawnStepName]; + BPWaitTimer *spawnTimer = [BPWaitTimer timerWithInterval:[context.config.launchTimeout doubleValue]]; + __block BPWaitTimer *executionTimer = [BPWaitTimer timerWithInterval:[BPUtils timeoutForAllTestsWithConfiguration:context.config]]; + // Start spawn timer; save execution timer for the spawn handler. + [spawnTimer start]; + BPApplicationLaunchHandler *spawnHandler = [BPApplicationLaunchHandler handlerWithTimer:spawnTimer]; + + __weak typeof(self) __self = self; + __weak typeof(spawnHandler) __spawnHandler = spawnHandler; + spawnHandler.beginWith = ^{ + [BPUtils printInfo:((__spawnHandler.pid > -1) ? INFO : ERROR) withString:@"Completed: %@", spawnStepName]; + }; + spawnHandler.onSuccess = ^{ + context.pid = __spawnHandler.pid; + + // At this point, we know that the execution itself has started. + [BPUtils printInfo:DEBUGINFO withString:@"XCTest execution spawned; waiting for execution to complete."]; + [[BPStats sharedStats] endTimer:spawnStepName withResult:@"INFO"]; + + // Start timer for actual xctest execution. + [BPUtils printInfo:INFO withString:@"%@", executeTestsStepName]; + [[BPStats sharedStats] startTimer:executeTestsStepName]; + [executionTimer start]; + }; + spawnHandler.onError = ^(NSError *error) { + [[BPStats sharedStats] endTimer:spawnStepName withResult:@"ERROR"]; + [BPUtils printInfo:ERROR withString:@"Could not spawn logic test execution: %@", [error localizedDescription]]; + NEXT([__self deleteSimulatorWithContext:context andStatus:BPExitStatusLaunchAppFailed]); + }; + spawnHandler.onTimeout = ^{ + [[BPStats sharedStats] addSimulatorLaunchFailure]; + [[BPStats sharedStats] endTimer:spawnStepName withResult:@"TIMEOUT"]; + [BPUtils printInfo:FAILED withString:@"Timeout: %@", spawnStepName]; + }; + + // 2) Handle execution finished + + // Now the handler for when the execution actually completes. + BPApplicationLaunchHandler *completionHandler = [BPApplicationLaunchHandler handlerWithTimer:executionTimer]; + completionHandler.onSuccess = ^{ + [BPUtils printInfo:DEBUGINFO withString:@"XCTest execution completed. Results must now be parsed."]; + [[BPStats sharedStats] endTimer:executeTestsStepName withResult:@"INFO"]; + NEXT([__self checkUnhostedProcessWithContext:context]); + }; + + completionHandler.onError = ^(NSError *error) { + [[BPStats sharedStats] endTimer:executeTestsStepName withResult:@"ERROR"]; + [BPUtils printInfo:ERROR withString:@"Spawned logic test execution failed: %@", [error localizedDescription]]; + + BPExitStatus exitStatus = BPExitStatusAppCrashed; + BOOL didhanderTimeout = error.domain == BPErrorDomain && error.code == BPHandler.timeoutErrorCode; + BOOL wasProcessKilledAfterTimeout = error.domain == BPErrorDomain && error.code == SIGKILL; + if (didhanderTimeout || wasProcessKilledAfterTimeout) { + exitStatus = BPExitStatusTestTimeout; + } + NEXT([__self deleteSimulatorWithContext:context andStatus:exitStatus]); + }; + + completionHandler.onTimeout = ^{ + [[BPStats sharedStats] addTestRuntimeTimeout]; + [[BPStats sharedStats] endTimer:executeTestsStepName withResult:@"TIMEOUT"]; + [BPUtils printInfo:FAILED withString:@"Timeout: %@", executeTestsStepName]; + }; + + [context.runner executeLogicTestsWithParser:context.parser + onSpawn:spawnHandler.defaultHandlerBlock + andCompletion:completionHandler.defaultHandlerBlock]; +} + - (void)launchApplicationWithContext:(BPExecutionContext *)context { NSString *stepName = LAUNCH_APPLICATION(context.attemptNumber); [BPUtils printInfo:INFO withString:@"%@", stepName]; @@ -448,6 +627,7 @@ - (void)connectTestBundleAndTestDaemonWithContext:(BPExecutionContext *)context NEXT([self checkProcessWithContext:context]); } + - (void)checkProcessWithContext:(BPExecutionContext *)context { BOOL isRunning = [self isProcessRunningWithContext:context]; if (!isRunning && [context.runner isFinished]) { @@ -470,7 +650,7 @@ - (void)checkProcessWithContext:(BPExecutionContext *)context { // If it's not running and we passed the above checks (e.g., the tests are not yet completed) // then it must mean the app has crashed. // However, we have a short-circuit for tests because those may not actually run any app - if (!isRunning && context.pid > 0 && [context.runner isApplicationLaunched] && !self.config.testing_NoAppWillRun) { + if (!isRunning && context.pid > 0 && ([context.runner isApplicationLaunched] || context.config.isLogicTestTarget) && !self.config.testing_NoAppWillRun) { // The tests ended before they even got started or the process is gone for some other reason [[BPStats sharedStats] endTimer:LAUNCH_APPLICATION(context.attemptNumber) withResult:@"APP CRASHED"]; [BPUtils printInfo:ERROR withString:@"Application crashed!"]; @@ -482,6 +662,30 @@ - (void)checkProcessWithContext:(BPExecutionContext *)context { NEXT_AFTER(1, [self checkProcessWithContext:context]); } +- (void)checkUnhostedProcessWithContext:(BPExecutionContext *)context { + // Handle tests + parsing is complete + BOOL isRunning = [self isProcessRunningWithContext:context]; + if (!isRunning && [context.runner isFinished]) { + [BPUtils printInfo:INFO withString:@"Finished test execution."]; + [[BPStats sharedStats] endTimer:EXECUTE_LOGIC_TEST(context.attemptNumber) withResult:[BPExitStatusHelper stringFromExitStatus:context.exitStatus]]; + [self runnerCompletedWithContext:context]; + return; + } + // Handle Simulator Crash + if (![context.runner isSimulatorRunning]) { + [[BPStats sharedStats] endTimer:EXECUTE_LOGIC_TEST(context.attemptNumber) withResult:@"SIMULATOR CRASHED"]; + [BPUtils printInfo:ERROR withString:@"SIMULATOR CRASHED!!!"]; + context.simulatorCrashed = YES; + [[BPStats sharedStats] addSimulatorCrash]; + [self deleteSimulatorWithContext:context andStatus:BPExitStatusSimulatorCrashed]; + return; + } + + // Even though the execution has completed, the parser may not be done yet. + // If we're here, that's the case... so try again in a second. + NEXT_AFTER(1, [self checkUnhostedProcessWithContext:context]); +} + - (BOOL)isProcessRunningWithContext:(BPExecutionContext *)context { if (self.config.testing_NoAppWillRun) { return NO; @@ -520,7 +724,7 @@ - (void)runnerCompletedWithContext:(BPExecutionContext *)context { [BPUtils printInfo:INFO withString:@"Saving Diagnostics for Debugging"]; [BPUtils saveDebuggingDiagnostics:_config.outputDirectory]; } - + [self deleteSimulatorWithContext:context andStatus:[context.runner exitStatus]]; } } @@ -702,6 +906,7 @@ - (NSString *)debugDescription { } #pragma mark - BPTestBundleConnectionDelegate + - (void)_XCT_launchProcessWithPath:(NSString *)path bundleID:(NSString *)bundleID arguments:(NSArray *)arguments environmentVariables:(NSDictionary *)environment { self.context.isTestRunnerContext = YES; [self installApplicationWithContext:self.context]; diff --git a/bp/src/PrivateHeaders/CoreSimulator/SimDevice.h b/bp/src/PrivateHeaders/CoreSimulator/SimDevice.h index 6619b46d..05a082da 100644 --- a/bp/src/PrivateHeaders/CoreSimulator/SimDevice.h +++ b/bp/src/PrivateHeaders/CoreSimulator/SimDevice.h @@ -11,6 +11,36 @@ typedef void (^CDUnknownBlockType)(void); typedef void (*CDUnknownFunctionPointerType)(void); @class NSArray, NSDate, NSDictionary, NSMachPort, NSMutableArray, NSMutableDictionary, NSObject, SimDeviceIO, NSString, NSUUID, SimDeviceBootInfo, SimDeviceNotificationManager, SimDevicePasteboard, SimDeviceSet, SimDeviceType, SimRuntime; +/** + A note on how to investigate expected usage for these private, class-dumped Apple APIs: + + It is often easier to get the below commands running in their commandline, `simctl` form first, in particular because + this is a public API that is (somewhat) documented. Fortunately for us, `simctl` uses `CoreSimulator` as well + as the APIs defined here in `SimDevice.h` under the hood, which we can use to our advantage to learn more about + expected usage. + + As an example, the below steps were used to learn more about the expected usage for the various `spawn...` methods + defined in this class, leveraging lldb: + + 1. Load lldb with a process such as `xcrun simctl spawn -s booted -XCTest All <.xctest_file>` + 2. It should pause the process before starting. Then add breakpoints to all variants of `spawn` from `SimDevice.h`, including: + a) `breakpoint set --selector spawnWithPath:options:terminationQueue:terminationHandler:pid:error:` + b) `breakpoint set --selector spawnWithPath:options:terminationQueue:terminationHandler:error:` + c) `breakpoint set --selector spawnWithPath:options:terminationHandler:error:` + d) `breakpoint set --selector _spawnFromSelfWithPath:options:terminationQueue:terminationHandler:error:` + e) `breakpoint set --selector _spawnFromLaunchdWithPath:options:terminationQueue:terminationHandler:error:` + f) `breakpoint set --selector _onBootstrapQueue_spawnWithPath:options:terminationQueue:terminationHandler:erro` + g) `breakpoint set --selector spawnWithPath:options:terminationQueue:terminationHandler:pid:error:` + h) `breakpoint set --selector spawnWithPath:options:terminationQueue:terminationHandler:error:` + 3. These breakpoints should be `pending`, as CoreSimulator won't have been loaded yet. Hit `c` to continue and unpause the process, + and continue to `c` through automatically triggered pauses as additional dylibs are loaded. + 4. Eventually you'll hit one of the above selectors' breakpoint. Use `register read` to print out all the registers to find what all's in frame. + 5. Then use `po ` for the register values to see what all objc classes are stored. + 6. Eventually, you'll find the values for the `path` and `options` arguments, or whatever other parameters you want to inspect. + + With this information, you can learn more how a given `simctl` command maps onto its corresponding `SimDevice` method :) + */ + @interface SimDevice : NSObject { unsigned long long _state; @@ -192,7 +222,7 @@ typedef void (*CDUnknownFunctionPointerType)(void); - (void)triggerCloudSyncWithCompletionHandler:(CDUnknownBlockType)arg1; - (void)launchApplicationAsyncWithID:(id)arg1 options:(id)arg2 completionHandler:(void (^)(NSError *, pid_t pid))arg3; - (int)spawnWithPath:(id)arg1 options:(id)arg2 terminationHandler:(CDUnknownBlockType)arg3 error:(id *)arg4; -- (void)spawnAsyncWithPath:(id)arg1 options:(id)arg2 terminationHandler:(CDUnknownBlockType)arg3 completionHandler:(CDUnknownBlockType)arg4; +- (void)spawnAsyncWithPath:(id)arg1 options:(id)arg2 terminationHandler:(void (^)(int))arg3 completionHandler:(void (^)(NSError *, pid_t pid))arg4; - (void)restoreContentsAndSettingsAsyncFromDevice:(id)arg1 completionHandler:(CDUnknownBlockType)arg2; - (void)eraseContentsAndSettingsAsyncWithCompletionHandler:(CDUnknownBlockType)arg1; - (void)renameAsync:(id)arg1 completionHandler:(CDUnknownBlockType)arg2; diff --git a/bp/src/SimulatorHelper.h b/bp/src/SimulatorHelper.h index a17227f6..a7f1ffdf 100644 --- a/bp/src/SimulatorHelper.h +++ b/bp/src/SimulatorHelper.h @@ -20,6 +20,16 @@ */ + (BOOL)loadFrameworksWithXcodePath:(NSString *)xcodePath; +/*! + * @discussion get xctest launch environment + * @param hostBundleID the bundleID of the host app + * @param device the device to run test + * @param config the configuration object + * @return returns the app launch environment as a dictionary + */ ++ (NSDictionary *)logicTestEnvironmentWithConfig:(BPConfiguration *)config + stdoutRelativePath:(NSString *)path; + /*! * @discussion get app launch environment * @param hostBundleID the bundleID of the host app @@ -30,6 +40,13 @@ + (NSDictionary *)appLaunchEnvironmentWithBundleID:(NSString *)hostBundleID device:(SimDevice *)device config:(BPConfiguration *)config; +/*! + * @discussion Creates an array of all tests that should be run, filtering out any tests that should be skipped. + * @param config the configuration object. + * @return The list of tests to run. + */ + ++ (NSArray *)testsToRunWithConfig:(BPConfiguration *)config; /*! * @discussion get the path of the environment configuration file @@ -38,6 +55,21 @@ */ + (NSString *)testEnvironmentWithConfiguration:(BPConfiguration *)config; +/*! + @discussion Creates a stdout file on the provided device of the form `/tmp/stdout_stderr_` + @param device The device to create the file on. + @return the path of the stdout file. + */ ++ (NSString *)makeStdoutFileOnDevice:(SimDevice *)device; + +/*! + @discussion Creates an output file on the provided device of the form `/tmp/BPTestInspector_testInfo_`. + This is meant to store the test info output generated by the injected test wrapper in an xctest execution. + @param device The device to create the file on. + @return the path of the output file. + */ ++ (NSString *)makeTestWrapperOutputFileOnDevice:(SimDevice *)device; + #pragma mark - Path Helper + (NSString *)bundleIdForPath:(NSString *)path; diff --git a/bp/src/SimulatorHelper.m b/bp/src/SimulatorHelper.m index d4d5cf25..26396420 100644 --- a/bp/src/SimulatorHelper.m +++ b/bp/src/SimulatorHelper.m @@ -11,9 +11,11 @@ #import "BPConfiguration.h" #import "BPUtils.h" #import "BPXCTestFile.h" +#import "SimDevice.h" #import "PrivateHeaders/XCTest/XCTestConfiguration.h" #import "PrivateHeaders/XCTest/XCTTestIdentifier.h" #import "PrivateHeaders/XCTest/XCTTestIdentifierSet.h" +#import @implementation SimulatorHelper @@ -61,6 +63,22 @@ + (BOOL)loadFrameworksWithXcodePath:(NSString *)xcodePath { return YES; } ++ (NSDictionary *)logicTestEnvironmentWithConfig:(BPConfiguration *)config + stdoutRelativePath:(NSString *)path { + NSMutableDictionary *environment = [@{ + kOptionsStdoutKey: path, + kOptionsStderrKey: path, + } mutableCopy]; + if (config.dyldFrameworkPath) { + environment[@"DYLD_FRAMEWORK_PATH"] = config.dyldFrameworkPath; + // DYLD_LIBRARY_PATH is required specifically for swift tests, which require libXCTestSwiftSupport, + // which must be findable in the library path. + environment[@"DYLD_LIBRARY_PATH"] = config.dyldFrameworkPath; + } + [environment addEntriesFromDictionary:config.environmentVariables]; + return [environment copy]; +} + + (NSDictionary *)appLaunchEnvironmentWithBundleID:(NSString *)hostBundleID device:(SimDevice *)device config:(BPConfiguration *)config { @@ -168,6 +186,70 @@ + (NSString *)bundleIdForPath:(NSString *)path { return bundleId; } ++ (NSArray *)testsToRunWithConfig:(BPConfiguration *)config { + // First, standardize all swift test names: + NSMutableArray *allTests = [self formatTestNamesForXCTest:config.allTestCases withConfig:config]; + NSMutableArray *testsToRun = [self formatTestNamesForXCTest:config.testCasesToRun withConfig:config]; + NSArray *testsToSkip = [self formatTestNamesForXCTest:config.testCasesToSkip withConfig:config]; + + // If there's no tests to skip, we can return these back otherwise unaltered. + // Otherwise, we'll need to remove any tests from `testsToRun` that are in our skip list. + if (testsToSkip.count == 0) { + return [(testsToRun ?: allTests) copy]; + } + + // If testCasesToRun was empty/nil, we default to all tests + if (testsToRun.count == 0) { + testsToRun = allTests; + } + [testsToRun filterUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString *testName, NSDictionary * _Nullable bindings) { + return ![testsToSkip containsObject:testName]; + }]]; + return [testsToRun copy]; +} + ++ (nullable NSMutableArray *)formatTestNamesForXCTest:(nullable NSArray *)tests + withConfig:(BPConfiguration *)config { + if (!tests) { + return nil; + } + [BPUtils printInfo:DEBUGINFO withString:@"Formatting test names. config.allTests: %@", config.allTests]; + NSMutableArray *formattedTests = [NSMutableArray array]; + for (NSString *testName in tests) { + BPTestCaseInfo *info = config.allTests[testName]; + if (info) { + [formattedTests addObject:info.standardizedFullName]; + } else { + [BPUtils printInfo:DEBUGINFO withString:@"Omitting false positive test method from test list: %@", testName]; + } + } + return formattedTests; +} + +// Intercept stdout, stderr and post as simulator-output events ++ (NSString *)makeStdoutFileOnDevice:(SimDevice *)device { + NSString *stdout_stderr = [NSString stringWithFormat:@"%@/tmp/stdout_stderr_%@", device.dataPath, [[device UDID] UUIDString]]; + return [self createFileWithPathTemplate:stdout_stderr]; +} + ++ (NSString *)makeTestWrapperOutputFileOnDevice:(SimDevice *)device { + NSString *stdout_stderr = [NSString stringWithFormat:@"%@/tmp/BPTestInspector_testInfo_%@", device.dataPath, [[device UDID] UUIDString]]; + return [self createFileWithPathTemplate:stdout_stderr]; +} + ++ (NSString *)createFileWithPathTemplate:(NSString *)pathTemplate { + NSString *fullPath = [BPUtils mkstemp:pathTemplate withError:nil]; + assert(fullPath != nil); + + [[NSFileManager defaultManager] removeItemAtPath:fullPath error:nil]; + + // Create empty file so we can tail it and the app can write to it + [[NSFileManager defaultManager] createFileAtPath:fullPath + contents:nil + attributes:nil]; + return fullPath; +} + + (NSString *)executablePathforPath:(NSString *)path { NSDictionary *appDic = [NSDictionary dictionaryWithContentsOfFile:[path stringByAppendingPathComponent:@"Info.plist"]]; NSString *appExecutable = [appDic objectForKey:(NSString *)kCFBundleExecutableKey]; diff --git a/bp/src/SimulatorMonitor.m b/bp/src/SimulatorMonitor.m index c8a5fad7..b7eec377 100644 --- a/bp/src/SimulatorMonitor.m +++ b/bp/src/SimulatorMonitor.m @@ -86,7 +86,10 @@ - (void)onTestCaseBeganWithName:(NSString *)testName inClass:(NSString *)testCla dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.maxTestExecutionTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if ([__self.currentTestName isEqualToString:testName] && [__self.currentClassName isEqualToString:testClass] && __self.testsState == Running) { [BPUtils printInfo:TIMEOUT withString:@"%10.6fs %@/%@", __self.maxTestExecutionTime, testClass, testName]; - [__self stopTestsWithErrorMessage:@"Test took too long to execute and was aborted." forTestName:testName inClass:testClass]; + [__self stopTestsWithErrorMessage:@"Test took too long to execute and was aborted." + forTestName:testName + inClass:testClass + shouldRetryTest:YES]; __self.exitStatus = BPExitStatusTestTimeout; [[BPStats sharedStats] endTimer:[NSString stringWithFormat:TEST_CASE_FORMAT, [BPStats sharedStats].attemptNumber, testClass, testName] withResult:@"ERROR"]; [[BPStats sharedStats] addTestRuntimeTimeout]; @@ -100,6 +103,9 @@ - (void)onTestCasePassedWithName:(NSString *)testName inClass:(NSString *)testCl [BPUtils printInfo:PASSED withString:@"%10.6fs %@/%@", [currentTime timeIntervalSinceDate:self.lastTestCaseStartDate], testClass, testName]; + NSLog(@"%10.6fs %@/%@", + [currentTime timeIntervalSinceDate:self.lastTestCaseStartDate], + testClass, testName); // Passing or failing means that if the simulator crashes later, we shouldn't rerun this test. [self updateExecutedTestCaseList:testName inClass:testClass]; @@ -189,7 +195,7 @@ - (void)onOutputReceived:(NSString *)output { __block NSUInteger previousOutputId = self.currentOutputId; __weak typeof(self) __self = self; - // App crashed + // App or logic test's XCTest execution crashed. if ([output isEqualToString:@"BP_APP_PROC_ENDED"]) { if (__self.testsState == Running || __self.testsState == Idle) { NSString *testClass = (__self.currentClassName ?: __self.previousClassName); @@ -208,7 +214,8 @@ - (void)onOutputReceived:(NSString *)output { } [self stopTestsWithErrorMessage:@"App Crashed" forTestName:(self.currentTestName ?: self.previousTestName) - inClass:(self.currentClassName ?: self.previousClassName)]; + inClass:(self.currentClassName ?: self.previousClassName) + shouldRetryTest:self.config.retryAppCrashTests]; self.exitStatus = BPExitStatusAppCrashed; [[BPStats sharedStats] addApplicationCrash]; } @@ -231,17 +238,18 @@ - (void)onOutputReceived:(NSString *)output { __self.exitStatus = testsReallyStarted ? BPExitStatusTestTimeout : BPExitStatusSimulatorCrashed; [__self stopTestsWithErrorMessage:@"Timed out waiting for the test to produce output. Test was aborted." forTestName:testName - inClass:testClass]; + inClass:testClass + shouldRetryTest:YES]; [[BPStats sharedStats] addTestOutputTimeout]; } }); self.lastOutput = currentTime; } -- (void)stopTestsWithErrorMessage:(NSString *)message forTestName:(NSString *)testName inClass:(NSString *)testClass { +- (void)stopTestsWithErrorMessage:(NSString *)message forTestName:(NSString *)testName inClass:(NSString *)testClass shouldRetryTest:(BOOL)shouldRetryTest { // Timeout or crash on a test means we should skip it when we rerun the tests, unless we've enabled re-running failed tests - if (!self.config.onlyRetryFailed) { + if (!shouldRetryTest) { [self updateExecutedTestCaseList:testName inClass:testClass]; } if (self.appState == Running && !self.config.testing_NoAppWillRun) { diff --git a/bp/tests/BPCLITests.m b/bp/tests/BPCLITests.m index 8ef51a7b..24a80378 100644 --- a/bp/tests/BPCLITests.m +++ b/bp/tests/BPCLITests.m @@ -43,6 +43,7 @@ - (void)testListArguments { [config saveOpt:[NSNumber numberWithInt:'N'] withArg:[NSString stringWithUTF8String:"foo"]]; [config saveOpt:[NSNumber numberWithInt:'N'] withArg:[NSString stringWithUTF8String:"bar"]]; [config saveOpt:[NSNumber numberWithInt:'N'] withArg:[NSString stringWithUTF8String:"baz"]]; + [config saveOpt:[NSNumber numberWithInt:369] withArg:@"YES"]; NSError *err; BOOL result; @@ -54,6 +55,7 @@ - (void)testListArguments { XCTAssertEqualObjects(config.errorRetriesCount, @2); XCTAssertEqualObjects(config.failureTolerance, @1); XCTAssertEqualObjects(config.numSims, @5); + XCTAssert(config.isLogicTestTarget); } - (void)testIgnoringAdditionalTestBundles { diff --git a/bp/tests/BPIntTestCase.m b/bp/tests/BPIntTestCase.m index a800a44f..7cd71e86 100644 --- a/bp/tests/BPIntTestCase.m +++ b/bp/tests/BPIntTestCase.m @@ -13,6 +13,7 @@ #import "BPIntTestCase.h" #import "BPConfiguration.h" #import "BPTestHelper.h" +#import "BPTestUtils.h" #import "BPUtils.h" #import "SimDeviceType.h" #import "SimRuntime.h" @@ -24,47 +25,8 @@ - (void)setUp { [super setUp]; self.continueAfterFailure = NO; - NSString *hostApplicationPath = [BPTestHelper sampleAppPath]; - NSString *testBundlePath = [BPTestHelper sampleAppNegativeTestsBundlePath]; - self.config = [[BPConfiguration alloc] initWithProgram:BP_BINARY]; - self.config.testBundlePath = testBundlePath; - self.config.appBundlePath = hostApplicationPath; - self.config.stuckTimeout = @40; - self.config.xcodePath = [BPUtils runShell:@"/usr/bin/xcode-select -print-path"]; - self.config.runtime = @BP_DEFAULT_RUNTIME; - self.config.repeatTestsCount = @1; - self.config.errorRetriesCount = @0; - self.config.testCaseTimeout = @20; - self.config.deviceType = @BP_DEFAULT_DEVICE_TYPE; - self.config.headlessMode = YES; - self.config.videoPaths = @[[BPTestHelper sampleVideoPath]]; - self.config.testRunnerAppPath = nil; - self.config.testing_CrashAppOnLaunch = NO; - self.config.cloneSimulator = NO; [BPUtils quietMode:[BPUtils isBuildScript]]; [BPUtils enableDebugOutput:NO]; - - NSError *err; - SimServiceContext *sc = [SimServiceContext sharedServiceContextForDeveloperDir:self.config.xcodePath error:&err]; - if (!sc) { NSLog(@"Failed to initialize SimServiceContext: %@", err); } - - for (SimDeviceType *type in [sc supportedDeviceTypes]) { - if ([[type name] isEqualToString:self.config.deviceType]) { - self.config.simDeviceType = type; - break; - } - } - - XCTAssert(self.config.simDeviceType != nil); - - for (SimRuntime *runtime in [sc supportedRuntimes]) { - if ([[runtime name] containsString:self.config.runtime]) { - self.config.simRuntime = runtime; - break; - } - } - - XCTAssert(self.config.simRuntime != nil); } @end diff --git a/bp/tests/BPReportTests.m b/bp/tests/BPReportTests.m index 496eb3fd..c849ad1d 100644 --- a/bp/tests/BPReportTests.m +++ b/bp/tests/BPReportTests.m @@ -14,13 +14,17 @@ #import "BPSimulator.h" #import "BPTestHelper.h" #import "BPUtils.h" +#import "BPTestUtils.h" @interface BPReportTests : BPIntTestCase @end @implementation BPReportTests - +- (void)setUp { + [super setUp]; + self.config = [BPTestUtils makeHostedTestConfiguration]; +} - (void)testReportWithAppCrashingTestsSet { [BPUtils enableDebugOutput:NO]; diff --git a/bp/tests/BPTestHelper.h b/bp/tests/BPTestHelper.h index b4d4ffcc..aa7ee7a7 100644 --- a/bp/tests/BPTestHelper.h +++ b/bp/tests/BPTestHelper.h @@ -17,6 +17,22 @@ // Return the path to the test plan json file. The json is packed into the app bundle as resource + (NSString *)testPlanPath; +// Return the path to logic tests, that are run unhosted rather than on the Sample App ++ (NSString *)logicTestBundlePath; + +// Return the path to logic tests, that are run unhosted rather than on the Sample App +// This particular bundle will only have passing tests to make certain functionalities easier to test. ++ (NSString *)passingLogicTestBundlePath; + +// A pre-built test bundle that will always be in x86_64 ++ (NSString *)logicTestBundlePath_x86_64; + +// A pre-built test bundle containing swift tests that will always be in x86_64. ++ (NSString *)logicTestBundlePath_swift_x86_64; + +// A pre-built test bundle that will always be in arm64 ++ (NSString *)logicTestBundlePath_arm64; + // Return the path to the sample app's xctest with new test cases + (NSString *)sampleAppNewTestsBundlePath; diff --git a/bp/tests/BPTestHelper.m b/bp/tests/BPTestHelper.m index 6ff7c84a..d50da15c 100644 --- a/bp/tests/BPTestHelper.m +++ b/bp/tests/BPTestHelper.m @@ -20,6 +20,29 @@ + (NSString *)testPlanPath { return [[self sampleAppPath] stringByAppendingPathComponent:@"test_plan.json"]; } +// Return the path to logic tests, that are run unhosted rather than on the Sampple App ++ (NSString *)logicTestBundlePath { + return [[self sampleAppPath] stringByAppendingPathComponent:@"/../BPLogicTests.xctest"]; +} + +// Return the path to logic tests, that are run unhosted rather than on the Sampple App +// This particular bundle will only have passing tests to make certain functionalities easier to test. ++ (NSString *)passingLogicTestBundlePath { + return [[self sampleAppPath] stringByAppendingPathComponent:@"/../BPPassingLogicTests.xctest"]; +} + ++ (NSString *)logicTestBundlePath_x86_64 { + return [[NSBundle bundleWithIdentifier:@"LI.BluepillRunnerTests"] pathForResource:@"BPLogicTestFixture_x86_64" ofType:@"xctest"]; +} + ++ (NSString *)logicTestBundlePath_swift_x86_64 { + return [[NSBundle bundleWithIdentifier:@"LI.BluepillRunnerTests"] pathForResource:@"BPLogicTestFixture_swift_x86_64" ofType:@"xctest"]; +} + ++ (NSString *)logicTestBundlePath_arm64 { + return [[NSBundle bundleWithIdentifier:@"LI.BluepillRunnerTests"] pathForResource:@"BPLogicTestFixture_arm64" ofType:@"xctest"]; +} + // Return the path to the sample app's xctest with new test cases + (NSString *)sampleAppNewTestsBundlePath { return [[self sampleAppPath] stringByAppendingString:@"/PlugIns/BPSampleAppNewTests.xctest"]; diff --git a/bp/tests/BluepillTests.m b/bp/tests/BluepillHostedTests.m similarity index 98% rename from bp/tests/BluepillTests.m rename to bp/tests/BluepillHostedTests.m index 1c0be3f9..85988d14 100644 --- a/bp/tests/BluepillTests.m +++ b/bp/tests/BluepillHostedTests.m @@ -16,6 +16,7 @@ #import "BPTestHelper.h" #import "BPUtils.h" #import "BPSimulator.h" +#import "BPTestUtils.h" #import "SimDevice.h" @@ -24,15 +25,14 @@ * - Exit code testing * - Report validation */ -@interface BluepillTests : BPIntTestCase +@interface BluepillHostedTests : BPIntTestCase @end -@implementation BluepillTests +@implementation BluepillHostedTests - - -- (void)tearDown { - [super tearDown]; +- (void)setUp { + [super setUp]; + self.config = [BPTestUtils makeHostedTestConfiguration]; } - (void)testAppThatCrashesOnLaunch { diff --git a/bp/tests/Unhosted Tests/BPTestCaseInfoTests.m b/bp/tests/Unhosted Tests/BPTestCaseInfoTests.m new file mode 100644 index 00000000..74240146 --- /dev/null +++ b/bp/tests/Unhosted Tests/BPTestCaseInfoTests.m @@ -0,0 +1,42 @@ +// Copyright 2016 LinkedIn Corporation +// Licensed under the BSD 2-Clause License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://opensource.org/licenses/BSD-2-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +#import +#import + +#import + +@interface BPTestCaseInfoTests : XCTestCase + +@end + +@implementation BPTestCaseInfoTests + +- (void)testArchiving { + // Mock data + BPTestCaseInfo *info1 = [[BPTestCaseInfo alloc] initWithClassName:@"Class" methodName:@"Method1"]; + BPTestCaseInfo *info2 = [[BPTestCaseInfo alloc] initWithClassName:@"Class" methodName:@"Method2"]; + BPTestCaseInfo *info3 = [[BPTestCaseInfo alloc] initWithClassName:@"Class" methodName:@"Method3"]; + NSArray *testCasesIn = @[info1, info2, info3]; + // Archive + NSError *error; + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:testCasesIn requiringSecureCoding:NO error:&error]; + // Unarchive + NSArray *testCasesOut = [NSKeyedUnarchiver unarchivedArrayOfObjectsOfClass:BPTestCaseInfo.class + fromData:data + error:&error]; + // Validate + XCTAssertNotNil(testCasesOut); + XCTAssertEqual(testCasesIn.count, testCasesOut.count); + for (int i = 0; i < testCasesIn.count; i++) { + XCTAssertEqualObjects(testCasesIn[i], testCasesOut[i]); + } +} + +@end diff --git a/bp/tests/Unhosted Tests/BluepillUnhostedBatchingTests.m b/bp/tests/Unhosted Tests/BluepillUnhostedBatchingTests.m new file mode 100644 index 00000000..9b326f21 --- /dev/null +++ b/bp/tests/Unhosted Tests/BluepillUnhostedBatchingTests.m @@ -0,0 +1,125 @@ +// Copyright 2016 LinkedIn Corporation +// Licensed under the BSD 2-Clause License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://opensource.org/licenses/BSD-2-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +#import + +#import "Bluepill.h" +#import "BPIntTestCase.h" +#import "BPConfiguration.h" +#import "BPTestHelper.h" +#import "BPUtils.h" +#import "BPTestUtils.h" + +@interface BluepillUnhostedBatchingTests : BPIntTestCase +@end + +@implementation BluepillUnhostedBatchingTests + +- (void)setUp { + [super setUp]; + self.config = [BPTestUtils makeUnhostedTestConfiguration]; + self.config.numSims = @1; + self.config.stuckTimeout = @3; + self.config.testBundlePath = [BPTestHelper passingLogicTestBundlePath]; + + NSString *tempDir = NSTemporaryDirectory(); + NSError *error; + self.config.outputDirectory = [BPUtils mkdtemp:[NSString stringWithFormat:@"%@/TestLogsTempDir", tempDir] withError:&error]; +} + +- (void)testAllTests { + // This is redundant but made explicit here for test clarity + self.config.testCasesToRun = nil; + self.config.testCasesToSkip = nil; + + // Run Tests + BPExitStatus exitCode = [[[Bluepill alloc ] initWithConfiguration:self.config] run]; + [BPTestUtils assertExitStatus:exitCode matchesExpected:BPExitStatusAllTestsPassed]; + + // Check that test is started in both sets of logs. + for (NSString *testCase in [BluepillUnhostedBatchingTests allTestCases]) { + [BPTestUtils checkIfTestCase:testCase bundleName:@"BPPassingLogicTests" wasRunInLog:[self.config.outputDirectory stringByAppendingPathComponent:@"1-simulator.log"]]; + } +} + +- (void)testOptInToObjcTests { + [self validateOptInToTests:@[ + @"BPPassingLogicTests/testPassingLogicTest2", + @"BPPassingLogicTests/testPassingLogicTest3", + ]]; +} + +- (void)testOptInToSwiftTests { + [self validateOptInToTests:@[ + @"SwiftLogicTests/testPassingLogicTest1()", + ]]; +} + +- (void)testOptOutOfObjcTests { + [self validateOptOutOfTests:@[ + @"BPPassingLogicTests/testPassingLogicTest2", + @"BPPassingLogicTests/testPassingLogicTest3", + ]]; +} + +- (void)testOptOutOfSwiftTests { + [self validateOptOutOfTests:@[ + @"SwiftLogicTests/testPassingLogicTest3()", + ]]; +} + +#pragma mark - Helpers + +- (void)validateOptInToTests:(NSArray *)tests { + self.config.testCasesToRun = tests; + [self validateExactlyTheseTestsAreExecuted:tests]; +} + +- (void)validateOptOutOfTests:(NSArray *)tests { + self.config.testCasesToSkip = tests; + NSArray *expectedTests = [BluepillUnhostedBatchingTests allTestsExcept:tests]; + [self validateExactlyTheseTestsAreExecuted:expectedTests]; +} + +- (void)validateExactlyTheseTestsAreExecuted:(NSArray *)tests { + // Run Tests + BPExitStatus exitCode = [[[Bluepill alloc ] initWithConfiguration:self.config] run]; + [BPTestUtils assertExitStatus:exitCode matchesExpected:BPExitStatusAllTestsPassed]; + + // Check that exclusively these tests were run. + for (NSString *testCase in tests) { + NSLog(@"testCase: %@", testCase); + XCTAssert([BPTestUtils checkIfTestCase:testCase bundleName:@"BPPassingLogicTests" wasRunInLog:[self.config.outputDirectory stringByAppendingPathComponent:@"1-simulator.log"]]); + } + + // Check that "skipped" tests are not run. + for (NSString *testCase in [BluepillUnhostedBatchingTests allTestsExcept:tests]) { + XCTAssertFalse([BPTestUtils checkIfTestCase:testCase bundleName:@"BPPassingLogicTests" wasRunInLog:[self.config.outputDirectory stringByAppendingPathComponent:@"1-simulator.log"]]); + } +} + ++ (NSArray *)allTestCases { + return @[ + @"BPPassingLogicTests/testPassingLogicTest1", + @"BPPassingLogicTests/testPassingLogicTest2", + @"BPPassingLogicTests/testPassingLogicTest3", + @"BPPassingLogicTests/testPassingLogicTest4", + @"SwiftLogicTests/testPassingLogicTest1()", + @"SwiftLogicTests/testPassingLogicTest2()", + @"SwiftLogicTests/testPassingLogicTest3()", + ]; +} + ++ (NSArray *)allTestsExcept:(NSArray *)omittedTests { + NSMutableArray *mutableTests = [[self allTestCases] mutableCopy]; + [mutableTests removeObjectsInArray:omittedTests]; + return [mutableTests copy]; +} + +@end diff --git a/bp/tests/Unhosted Tests/BluepillUnhostedTests.m b/bp/tests/Unhosted Tests/BluepillUnhostedTests.m new file mode 100644 index 00000000..764665d5 --- /dev/null +++ b/bp/tests/Unhosted Tests/BluepillUnhostedTests.m @@ -0,0 +1,176 @@ +// Copyright 2016 LinkedIn Corporation +// Licensed under the BSD 2-Clause License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://opensource.org/licenses/BSD-2-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +#import +#import + +#import "Bluepill.h" +#import "BPIntTestCase.h" +#import "BPConfiguration.h" +#import "BPTestHelper.h" +#import "BPUtils.h" +#import "BPTestUtils.h" + +/** + * This test suite is the integration tests to make sure logic tests are being run correctly. + * It includes validation on the following: + * - Exit code testing + * - Failure/Timeout/Crash handling + * - Retry behaviors + */ +@interface BluepillUnhostedTests : BPIntTestCase +@end + +@implementation BluepillUnhostedTests + +- (void)setUp { + [super setUp]; + self.config = [BPTestUtils makeUnhostedTestConfiguration]; + self.config.numSims = @1; + self.config.stuckTimeout = @1; + + NSString *testBundlePath = [BPTestHelper logicTestBundlePath]; + self.config.testBundlePath = testBundlePath; +} + +#pragma mark - Passing Tests + +- (void)testSinglePassingLogicTests { + self.config.testCasesToRun = @[@"BPLogicTests/testPassingLogicTest1"]; + BPExitStatus exitCode = [[[Bluepill alloc] initWithConfiguration:self.config] run]; + [BPTestUtils assertExitStatus:exitCode matchesExpected:BPExitStatusAllTestsPassed]; +} + +- (void)testMultiplePassingLogicTests { + NSString *tempDir = NSTemporaryDirectory(); + NSError *error; + NSString *outputDir = [BPUtils mkdtemp:[NSString stringWithFormat:@"%@/TestLogsTempDir", tempDir] withError:&error]; + self.config.outputDirectory = outputDir; + self.config.testCasesToRun = @[ + @"BPLogicTests/testPassingLogicTest1", + @"BPLogicTests/testPassingLogicTest2" + ]; + + // Run Tests + BPExitStatus exitCode = [[[Bluepill alloc ] initWithConfiguration:self.config] run]; + [BPTestUtils assertExitStatus:exitCode matchesExpected:BPExitStatusAllTestsPassed]; + + // Check that test is started in both sets of logs. + for (NSString *testCase in self.config.testCasesToRun) { + XCTAssert([BPTestUtils checkIfTestCase:testCase bundleName:@"BPLogicTests" wasRunInLog:[outputDir stringByAppendingPathComponent:@"1-simulator.log"]]); + } +} + +# pragma mark - Failing Tests + +- (void)testFailingLogicTest { + self.config.testCasesToRun = @[@"BPLogicTests/testFailingLogicTest"]; + + BPExitStatus exitCode = [[[Bluepill alloc ] initWithConfiguration:self.config] run]; + [BPTestUtils assertExitStatus:exitCode matchesExpected:BPExitStatusTestsFailed]; +} + +#pragma mark - Handling Crashes + +/* + A boring objective-c crash (such as index out of bounds on an NSArray) should be + handled smoothly by XCTest, and reported as such as a failed test. + */ +- (void)testCrashingTestCaseLogicTest { + self.config.testCasesToRun = @[@"BPLogicTests/testCrashTestCaseLogicTest"]; + BPExitStatus exitCode = [[[Bluepill alloc ] initWithConfiguration:self.config] run]; + [BPTestUtils assertExitStatus:exitCode matchesExpected:BPExitStatusTestsFailed]; +} + +/* + A more aggressive crash (like doing an illegal strcpy) will crash the entire XCTest + execution. + */ +- (void)testCrashingExecutionLogicTest { + self.config.testCasesToRun = @[@"BPLogicTests/testCrashExecutionLogicTest"]; + BPExitStatus exitCode = [[[Bluepill alloc ] initWithConfiguration:self.config] run]; + [BPTestUtils assertExitStatus:exitCode matchesExpected:BPExitStatusAppCrashed]; +} + +#pragma mark - Timeouts + +/* + This test validates that a test fails when the simulator has no output for over + the stuckTimeout threshold + */ +- (void)testStuckLogicTest { + self.config.testCasesToRun = @[@"BPLogicTests/testStuckLogicTest"]; + BPExitStatus exitCode = [[[Bluepill alloc] initWithConfiguration:self.config] run]; + [BPTestUtils assertExitStatus:exitCode matchesExpected:BPExitStatusTestTimeout]; +} + +/* + This test validates that a slow test will fail, even when the simulator sees + some change. + */ +- (void)testHangingLogicTest { + // `BPLogicTests/testSlowLogicTest` is designed to log a string infinitely, once a second. + // As a result, it should not "get stuck", but should eventually timeout anyway. + self.config.stuckTimeout = @1; + self.config.testCaseTimeout = @3; + self.config.testCasesToRun = @[@"BPLogicTests/testSlowLogicTest"]; + + BPExitStatus exitCode = [[[Bluepill alloc] initWithConfiguration:self.config] run]; + [BPTestUtils assertExitStatus:exitCode matchesExpected:BPExitStatusTestTimeout]; +} + +/* + The timeout should only cause a failure if an individual test exceeds the timeout, + not if the combined time sums to above the timeout. + */ +- (void)testTimeoutOnlyAppliesToTestCaseNotSuite { + // The three tests combined should exceed any timeouts, but that shouldn't be a problem. + self.config.testCaseTimeout = @2; + self.config.stuckTimeout = @2; + self.config.testCasesToRun = @[ + @"BPLogicTests/testOneSecondTest1", + @"BPLogicTests/testOneSecondTest2", + @"BPLogicTests/testOneSecondTest3", + ]; + BPExitStatus exitCode = [[[Bluepill alloc ] initWithConfiguration:self.config] run]; + [BPTestUtils assertExitStatus:exitCode matchesExpected:BPExitStatusAllTestsPassed]; +} + +#pragma mark - Retries + +- (void)testRetriesFailure { + [self validateTestIsRetried:@"BPLogicTests/testFailingLogicTest"]; +} + +- (void)testRetriesCrash { + self.config.retryAppCrashTests = YES; + [self validateTestIsRetried:@"BPLogicTests/testCrashExecutionLogicTest"]; +} + +#pragma mark - Helpers + +- (void)validateTestIsRetried:(NSString *)testCase { + // Setup + NSString *tempDir = NSTemporaryDirectory(); + NSError *error; + NSString *outputDir = [BPUtils mkdtemp:[NSString stringWithFormat:@"%@/FailingTestsSetTempDir", tempDir] withError:&error]; + self.config.outputDirectory = outputDir; + self.config.errorRetriesCount = @1; + self.config.failureTolerance = @1; + self.config.testCasesToRun = @[testCase]; + + // Run Tests + __unused BPExitStatus exitCode = [[[Bluepill alloc ] initWithConfiguration:self.config] run]; + + // Validate + XCTAssert([BPTestUtils checkIfTestCase:testCase bundleName:@"BPLogicTests" wasRunInLog:[outputDir stringByAppendingPathComponent:@"1-simulator.log"]]); + XCTAssert([BPTestUtils checkIfTestCase:testCase bundleName:@"BPLogicTests" wasRunInLog:[outputDir stringByAppendingPathComponent:@"2-simulator.log"]]); +} + +@end diff --git a/bp/tests/Utils/BPTestUtils.h b/bp/tests/Utils/BPTestUtils.h new file mode 100644 index 00000000..45717733 --- /dev/null +++ b/bp/tests/Utils/BPTestUtils.h @@ -0,0 +1,29 @@ +// Copyright 2016 LinkedIn Corporation +// Licensed under the BSD 2-Clause License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://opensource.org/licenses/BSD-2-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +#import +#import "BPExitStatus.h" + +@class BPConfiguration; + +NS_ASSUME_NONNULL_BEGIN + +@interface BPTestUtils : NSObject + ++ (nonnull BPConfiguration *)makeUnhostedTestConfiguration; + ++ (nonnull BPConfiguration *)makeHostedTestConfiguration; + ++ (void)assertExitStatus:(BPExitStatus)exitStatus matchesExpected:(BPExitStatus)expectedStatus; + ++ (BOOL)checkIfTestCase:(NSString *)testCase bundleName:(NSString *)bundleName wasRunInLog:(NSString *)logPath; + +@end + +NS_ASSUME_NONNULL_END diff --git a/bp/tests/Utils/BPTestUtils.m b/bp/tests/Utils/BPTestUtils.m new file mode 100644 index 00000000..e73b3203 --- /dev/null +++ b/bp/tests/Utils/BPTestUtils.m @@ -0,0 +1,117 @@ +// Copyright 2016 LinkedIn Corporation +// Licensed under the BSD 2-Clause License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at https://opensource.org/licenses/BSD-2-Clause +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +#import "BPTestUtils.h" + +#import + +#import "BPConfiguration.h" +#import "BPTestHelper.h" +#import "BPUtils.h" +#import "SimDeviceType.h" +#import "SimRuntime.h" +#import "SimServiceContext.h" + +@implementation BPTestUtils + ++ (nonnull BPConfiguration *)makeUnhostedTestConfiguration { + BPConfiguration *config = [self makeDefaultTestConfiguration]; + config.testBundlePath = [BPTestHelper logicTestBundlePath]; + config.isLogicTestTarget = YES; + return config; +} + ++ (nonnull BPConfiguration *)makeHostedTestConfiguration { + BPConfiguration *config = [self makeDefaultTestConfiguration]; + config.appBundlePath = [BPTestHelper sampleAppPath]; + config.testBundlePath = [BPTestHelper sampleAppNegativeTestsBundlePath]; + config.isLogicTestTarget = NO; + return config; +} + ++ (nonnull BPConfiguration *)makeDefaultTestConfiguration { + BPConfiguration *config = [[BPConfiguration alloc] initWithProgram:BP_BINARY]; + config.stuckTimeout = @40; + config.xcodePath = [BPUtils runShell:@"/usr/bin/xcode-select -print-path"]; + config.runtime = @BP_DEFAULT_RUNTIME; + config.repeatTestsCount = @1; + config.errorRetriesCount = @0; + config.testCaseTimeout = @20; + config.deviceType = @BP_DEFAULT_DEVICE_TYPE; + config.headlessMode = YES; + config.videoPaths = @[[BPTestHelper sampleVideoPath]]; + config.testRunnerAppPath = nil; + config.testing_CrashAppOnLaunch = NO; + config.cloneSimulator = NO; + config.outputDirectory = @"/Users/lthrockm/Desktop/output/"; + + // Set up simulator device + runtime + NSError *err; + SimServiceContext *sc = [SimServiceContext sharedServiceContextForDeveloperDir:config.xcodePath error:&err]; + if (!sc) { NSLog(@"Failed to initialize SimServiceContext: %@", err); } + + for (SimDeviceType *type in [sc supportedDeviceTypes]) { + if ([[type name] isEqualToString:config.deviceType]) { + config.simDeviceType = type; + break; + } + } + XCTAssert(config.simDeviceType != nil); + + for (SimRuntime *runtime in [sc supportedRuntimes]) { + if ([[runtime name] containsString:config.runtime]) { + config.simRuntime = runtime; + break; + } + } + XCTAssert(config.simRuntime != nil); + + return config; +} + ++ (void)assertExitStatus:(BPExitStatus)exitStatus matchesExpected:(BPExitStatus)expectedStatus { + XCTAssert(exitStatus == expectedStatus, + @"Expected: %@ Got: %@", + [BPExitStatusHelper stringFromExitStatus:expectedStatus], + [BPExitStatusHelper stringFromExitStatus:exitStatus]); +} + ++ (BOOL)isTestSwiftTest:(NSString *)testName { + return [testName containsString:@"."] || [testName containsString:@"()"]; +} + ++ (NSString *)formatSwiftTestForXCTest:(NSString *)testName withBundleName:(NSString *)bundleName { + NSString *formattedName = testName; + // Remove parentheses + NSRange range = [formattedName rangeOfString:@"()"]; + if (range.location != NSNotFound) { + formattedName = [formattedName substringToIndex:range.location]; + } + // Add `.` + NSString *bundlePrefix = [bundleName stringByAppendingString:@"."]; + if (![formattedName containsString:bundlePrefix]) { + formattedName = [NSString stringWithFormat:@"%@.%@", bundleName, formattedName]; + } + return formattedName; +} + ++ (BOOL)checkIfTestCase:(NSString *)testCase bundleName:(NSString *)bundleName wasRunInLog:(NSString *)logPath { + NSString *testName = testCase; + if ([self isTestSwiftTest:testName]) { + testName = [BPTestUtils formatSwiftTestForXCTest:testName withBundleName:bundleName]; + } + NSArray *testComponents = [testName componentsSeparatedByString:@"/"]; + NSString *expectedString = [NSString stringWithFormat:@"Test Case '-[%@ %@]' started.", testComponents[0], testComponents[1]]; + NSString *log = [NSString stringWithContentsOfFile:logPath encoding:NSUTF8StringEncoding error:nil]; + XCTAssertNotNil(log); + NSLog(@"log: %@", log); + return [log rangeOfString:expectedString].location != NSNotFound; +} + +@end diff --git a/bptestrunner/BUILD.bazel b/bptestrunner/BUILD.bazel index 4250458f..323d406b 100644 --- a/bptestrunner/BUILD.bazel +++ b/bptestrunner/BUILD.bazel @@ -14,6 +14,18 @@ native_binary( out = "bp", ) +native_binary( + name = "libBPTestInspector.dylib", + src = 'bin/libBPTestInspector.dylib', + out = "libBPTestInspector.dylib", +) + +native_binary( + name = "libBPMacTestInspector.dylib", + src = 'bin/libBPMacTestInspector.dylib', + out = "libBPMacTestInspector.dylib", +) + exports_files([ "bluepill_batch_test_runner.template.sh" ]) diff --git a/bptestrunner/bluepill_batch_test.bzl b/bptestrunner/bluepill_batch_test.bzl index 36203270..349a709c 100644 --- a/bptestrunner/bluepill_batch_test.bzl +++ b/bptestrunner/bluepill_batch_test.bzl @@ -5,7 +5,7 @@ load( ) def _bluepill_batch_test_impl(ctx): - runfiles = [ctx.file._bp_exec, ctx.file._bluepill_exec] + runfiles = [ctx.file._bp_exec, ctx.file._bluepill_exec, ctx.file._libBPTestInspector_dylib, ctx.file._libBPMacTestInspector_dylib] test_bundle_paths = [] test_host_paths = [] @@ -27,15 +27,23 @@ def _bluepill_batch_test_impl(ctx): test_host_paths.append("\"{}\"".format(test_host.short_path)) runfiles.append(test_host) + #test_plan - test_plan = struct( - test_host = test_host.basename.split( - "." + test_host.extension, - )[0] + ".app", - environment = test_env, - arguments = test_env, - test_bundle_path = bundle_info.bundle_name + bundle_info.bundle_extension, - ) + if test_host: + test_plan = struct( + test_host = test_host.basename.split( + "." + test_host.extension, + )[0] + ".app", + environment = test_env, + arguments = test_env, + test_bundle_path = bundle_info.bundle_name + bundle_info.bundle_extension, + ) + else: + test_plan = struct( + environment = test_env, + arguments = test_env, + test_bundle_path = bundle_info.bundle_name + bundle_info.bundle_extension, + ) test_plans[test_target.label.name] = test_plan # Write test plan json. @@ -49,12 +57,17 @@ def _bluepill_batch_test_impl(ctx): # Write the shell script. substitutions = { "test_bundle_paths": " ".join(test_bundle_paths), - "test_host_paths": " ".join(test_host_paths), "bp_test_plan": test_plan_file.short_path, "bp_path": ctx.executable._bp_exec.short_path, "bluepill_path": ctx.executable._bluepill_exec.short_path, + "testInspector_path": ctx.file._libBPTestInspector_dylib.short_path, + "macTestInspector_path": ctx.file._libBPMacTestInspector_dylib.short_path, "target_name": ctx.attr.name, } + if len(test_host_paths) > 0: + substitutions["test_host_paths"] = " ".join(test_host_paths) + + if ctx.attr.config_file: runfiles.append(ctx.file.config_file) substitutions["bp_config_file"] = ctx.file.config_file.path @@ -114,6 +127,18 @@ as possible between simulators. executable = True, cfg = "host", ), + "_libBPTestInspector_dylib": attr.label( + default = Label( + "//:libBPTestInspector.dylib", + ), + allow_single_file = True, + ), + "_libBPMacTestInspector_dylib": attr.label( + default = Label( + "//:libBPMacTestInspector.dylib", + ), + allow_single_file = True, + ), "_xcode_config": attr.label( default = configuration_field( fragment = "apple", diff --git a/bptestrunner/bluepill_batch_test_runner.template.sh b/bptestrunner/bluepill_batch_test_runner.template.sh index 012b9f38..44b9a235 100644 --- a/bptestrunner/bluepill_batch_test_runner.template.sh +++ b/bptestrunner/bluepill_batch_test_runner.template.sh @@ -18,12 +18,15 @@ BP_TEST_ESTIMATE_JSON="bp_test_time_estimates_json" BP_TEST_PLAN="bp_test_plan" BP_PATH="bp_path" BLUEPILL_PATH="bluepill_path" +TEST_INSPECTOR_PATH="testInspector_path" +MAC_TEST_INSPECTOR_PATH="macTestInspector_path" # Remove existing working folder for a clean state rm -rf $BP_WORKING_FOLDER mkdir $BP_WORKING_FOLDER # Extract test bundles + for test_bundle in ${TEST_BUNDLE_PATHS[@]}; do if [[ $test_bundle == *.zip ]]; then tar -C $BP_WORKING_FOLDER -xzf $test_bundle @@ -36,20 +39,22 @@ for test_bundle in ${TEST_BUNDLE_PATHS[@]}; do fi done -# Clone and extract test hosts -for test_host in ${TEST_HOST_PATHS[@]}; do - if [[ "$test_host" == *.ipa ]]; then - TEST_HOST_NAME=$(basename_without_extension "${test_host}") - unzip -qq -d "$BP_WORKING_FOLDER" "$test_host" - cp -cr "${BP_WORKING_FOLDER}/Payload/${TEST_HOST_NAME}.app" ${BP_WORKING_FOLDER} - elif [[ $test_host == *.app ]]; then - cp -cr $test_host $BP_WORKING_FOLDER - chmod -R ug+w "$BP_WORKING_FOLDER/$(basename "$test_host")" - else - echo "$test_host is not an ipa file or app bundle." - exit 1 - fi -done +# Clone and extract test hosts (won't be set for logic tests) +if [ ! -z ${test_host_paths+x} ]; then + for test_host in ${TEST_HOST_PATHS[@]}; do + if [[ "$test_host" == *.ipa ]]; then + TEST_HOST_NAME=$(basename_without_extension "${test_host}") + unzip -qq -d "$BP_WORKING_FOLDER" "$test_host" + cp -cr "${BP_WORKING_FOLDER}/Payload/${TEST_HOST_NAME}.app" ${BP_WORKING_FOLDER} + elif [[ $test_host == *.app ]]; then + cp -cr $test_host $BP_WORKING_FOLDER + chmod -R ug+w "$BP_WORKING_FOLDER/$(basename "$test_host")" + else + echo "$test_host is not an ipa file or app bundle." + exit 1 + fi + done +fi # Copy config file to bp working folder if [ -f "$BP_CONFIG_FILE" ]; then @@ -69,9 +74,18 @@ fi sed 's/$TEST_UNDECLARED_OUTPUTS_DIR/'"${TEST_UNDECLARED_OUTPUTS_DIR//\//\\/}"'/g' $BP_TEST_PLAN > $BP_WORKING_FOLDER/$BP_TEST_PLAN BP_TEST_PLAN_ARG="$(basename "$BP_TEST_PLAN")" -# Copy bluepill and bp executables to working folder +# Copy bluepill and bp executables to working folder, along with testInspector dylib. cp "$BP_PATH" $BP_WORKING_FOLDER cp "$BLUEPILL_PATH" $BP_WORKING_FOLDER +cp "$TEST_INSPECTOR_PATH" $BP_WORKING_FOLDER +cp "$MAC_TEST_INSPECTOR_PATH" $BP_WORKING_FOLDER + +echo "pwd" +pwd +echo "LTHROCKM DEBUG - TEST_INSPECTOR_PATH: $TEST_INSPECTOR_PATH" +echo "LTHROCKM DEBUG - BP_WORKING_FOLDER: $BP_WORKING_FOLDER" + +export "DYLD_LIBRARY_PATH=.:$BP_WORKING_FOLDER/libBPMacTestInspector.dylib" # Run bluepill # NOTE: we override output folder here and disregard the one in the config file. diff --git a/scripts/bluepill.sh b/scripts/bluepill.sh index 3ab8bc00..e3254cb2 100755 --- a/scripts/bluepill.sh +++ b/scripts/bluepill.sh @@ -36,6 +36,27 @@ mkdir -p build/ bluepill_build() { set -o pipefail + + # First build BPTestInspector, as it's a dependency. + xcodebuild \ + -workspace Bluepill.xcworkspace \ + -arch x86_64 \ + -scheme BPTestInspector \ + -sdk iphonesimulator \ + -configuration Release \ + -derivedDataPath "$DerivedDataPath" | tee result_bptestinspector.txt | $XCPRETTY + test $? == 0 || { + echo Build failed + xcodebuild -list -workspace Bluepill.xcworkspace + -scheme BPTestInspector \ + cat result_bptestinspector.txt + exit 1 + } + test -x build/Build/Products/Release-iphonesimulator/libBPTestInspector.dylib || { + echo No bp built + exit 1 + } + xcodebuild \ -workspace Bluepill.xcworkspace \ -scheme bluepill \ @@ -52,12 +73,15 @@ bluepill_build() echo No bp built exit 1 } + set +o pipefail # package bluepill TAG=$(git describe --always --tags) DST="Bluepill-$TAG" mkdir -p "build/$DST/bin" - cp build/Build/Products/Release/{bp,bluepill} "build/$DST/bin" + cp build/Build/Products/Release/{bp,bluepill,libBPMacTestInspector.dylib,libBPTestInspector.dylib} "build/$DST/bin" + cp build/Build/Products/Release-iphonesimulator/libBPTestInspector.dylib "build/$DST/bin" + ## build the man page mkdir -p "build/$DST/man/man1" /usr/bin/python scripts/man.py "build/$DST/man/man1/bluepill.1"