From 65f8a7bbf29346deca81c2f114307b5734875703 Mon Sep 17 00:00:00 2001 From: Pavel Punegov Date: Mon, 7 Oct 2019 16:06:52 +0300 Subject: [PATCH] Execute tests on iOS using xcodebuild and idb --- backend.native/tests/build.gradle | 34 +- backend.native/tests/iosLauncher/.gitignore | 1 + .../project.pbxproj | 381 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 10 + .../AppIcon.appiconset/Contents.json | 98 +++++ .../Base.lproj/LaunchScreen.storyboard | 25 ++ .../Base.lproj/Main.storyboard | 48 +++ .../iosLauncher/KonanTestLauncher/Info.plist | 45 +++ .../org/jetbrains/kotlin/ExecutorService.kt | 269 ++++++++++++- .../org/jetbrains/kotlin/FrameworkTest.kt | 42 +- .../jetbrains/kotlin/KonanTestExecutable.kt | 28 ++ .../org/jetbrains/kotlin/KotlinNativeTest.kt | 54 +-- .../main/kotlin/org/jetbrains/kotlin/Utils.kt | 7 +- 15 files changed, 993 insertions(+), 64 deletions(-) create mode 100644 backend.native/tests/iosLauncher/.gitignore create mode 100644 backend.native/tests/iosLauncher/KonanTestLauncher.xcodeproj/project.pbxproj create mode 100644 backend.native/tests/iosLauncher/KonanTestLauncher.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 backend.native/tests/iosLauncher/KonanTestLauncher.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 backend.native/tests/iosLauncher/KonanTestLauncher.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 backend.native/tests/iosLauncher/KonanTestLauncher/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 backend.native/tests/iosLauncher/KonanTestLauncher/Base.lproj/LaunchScreen.storyboard create mode 100644 backend.native/tests/iosLauncher/KonanTestLauncher/Base.lproj/Main.storyboard create mode 100644 backend.native/tests/iosLauncher/KonanTestLauncher/Info.plist create mode 100644 build-tools/src/main/kotlin/org/jetbrains/kotlin/KonanTestExecutable.kt diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 87532118655..060f8295408 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -2278,7 +2278,7 @@ task extend_exception(type: KonanLocalTest) { } standaloneTest("check_stacktrace_format") { - disabled = !isAppleTarget(project) || project.globalTestArgs.contains('-opt') + disabled = !isAppleTarget(project) || project.globalTestArgs.contains('-opt') || (project.testTarget == 'ios_arm64') flags = ['-g'] source = "runtime/exceptions/check_stacktrace_format.kt" } @@ -3557,7 +3557,7 @@ interopTest("interop_objc_smoke") { flags += "-nodefaultlibs" } - doBefore { + doBeforeBuild { execKonanClang(project.target) { args "$projectDir/interop/objc/smoke.m" args "-lobjc", '-fobjc-arc' @@ -3582,7 +3582,7 @@ interopTest("interop_objc_global_initializer") { interop = 'objcMisc' flags = ['-g'] - doBefore { + doBeforeBuild { execKonanClang(project.target) { args "$projectDir/interop/objc_with_initializer/objc_misc.m" args "-lobjc", '-fobjc-arc' @@ -3600,7 +3600,7 @@ interopTest("interop_objc_msg_send") { source = "interop/objc/msg_send/main.kt" interop = 'objcMessaging' - doBefore { + doBeforeBuild { execKonanClang(project.target) { args "$projectDir/interop/objc/msg_send/messaging.m" args "-lobjc", '-fobjc-arc' @@ -3620,7 +3620,7 @@ interopTest("interop_objc_kt34467") { } standaloneTest("jsinterop_math") { - doBefore { + doBeforeBuild { def jsinteropScript = isWindows() ? "jsinterop.bat" : "jsinterop" def jsinterop = "$dist/bin/$jsinteropScript" // TODO: We probably need a NativeInteropPlugin for jsinterop? @@ -3688,7 +3688,7 @@ task library_mismatch(type: KonanDriverTest) { dependsOn ':wasm32CrossDistEndorsedLibraries' } - doBefore { + doBeforeBuild { def konancScript = isWindows() ? "konanc.bat" : "konanc" def konanc = "$dist/bin/$konancScript" "$konanc $lib -p library -o $dir/1.2/empty -target $currentTarget -lv 1.2".execute().waitFor() @@ -3714,7 +3714,7 @@ task library_ir_provider_mismatch(type: KonanDriverTest) { def currentTarget = project.target.name - doBefore { + doBeforeBuild { def konancScript = isWindows() ? "konanc.bat" : "konanc" def konanc = "$dist/bin/$konancScript" "$konanc $lib -p library -o $dir/supported_ir_provider/empty -target $currentTarget".execute().waitFor() @@ -3843,6 +3843,7 @@ if (isAppleTarget(project)) { dependsOn ":${targetName}CrossDistRuntime", ':commonDistRuntime', ':distCompiler' } + extraOpts "-Xembed-bitcode" extraOpts project.globalTestArgs } @@ -3855,6 +3856,7 @@ if (isAppleTarget(project)) { dependsOn ":${targetName}CrossDistRuntime", ':commonDistRuntime', ':distCompiler' } + extraOpts "-Xembed-bitcode" extraOpts project.globalTestArgs } } @@ -4093,4 +4095,22 @@ pluginTest("runtime_basic_init", "nopPlugin") { dependencies { nopPluginCompile kotlinCompilerModule +} + +// Configure ios build +if (PlatformInfo.getTarget(project) == KonanTarget.IOS_ARM64.INSTANCE) { + project.tasks + .matching { it instanceof KonanTestExecutable } + .all { + ExecutorServiceKt.configureXcodeBuild((KonanTestExecutable) it) + } + // Exclude tasks that can not be run on device + project.tasks + .matching { (it instanceof KonanLocalTest && ((KonanLocalTest) it).testData != null) || + // Filter out tests that depend on libs. TODO: copy them too + it instanceof KonanInteropTest || + it instanceof KonanDynamicTest || + it instanceof KonanLinkTest + } + .all { it.enabled = false } } \ No newline at end of file diff --git a/backend.native/tests/iosLauncher/.gitignore b/backend.native/tests/iosLauncher/.gitignore new file mode 100644 index 00000000000..4f405ccc66e --- /dev/null +++ b/backend.native/tests/iosLauncher/.gitignore @@ -0,0 +1 @@ +xcuserdata \ No newline at end of file diff --git a/backend.native/tests/iosLauncher/KonanTestLauncher.xcodeproj/project.pbxproj b/backend.native/tests/iosLauncher/KonanTestLauncher.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..0c736eaf44d --- /dev/null +++ b/backend.native/tests/iosLauncher/KonanTestLauncher.xcodeproj/project.pbxproj @@ -0,0 +1,381 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 6522238E98511C4FCE1C84DB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 65222E77B9880A666F68B517 /* Main.storyboard */; }; + 652225006B71BB79B6D0D726 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 652225CB5CBA03A47F5148C4 /* Assets.xcassets */; }; + 65222623D2703AEFB363121F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6522214B5DB6829939E3F325 /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 65222402702E398002CC649E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.info; path = Info.plist; sourceTree = ""; }; + 652225210978EC9016CB5EFF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 652225CB5CBA03A47F5148C4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 6522292479593142D11AD88A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 65222B6AE5EB6447D2D3E6ED /* KonanTestLauncher.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KonanTestLauncher.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 65222DAFA15A9AB521E55317 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 65222113FD4C67E6BA46614D /* gradle */ = { + isa = PBXGroup; + children = ( + 6522242531A4CAB63B2BFE71 /* wrapper */, + ); + path = gradle; + sourceTree = ""; + }; + 6522238C8FBBB7F836D071CF /* KonanTestLauncher */ = { + isa = PBXGroup; + children = ( + 65222402702E398002CC649E /* Info.plist */, + 652225CB5CBA03A47F5148C4 /* Assets.xcassets */, + 6522214B5DB6829939E3F325 /* LaunchScreen.storyboard */, + 65222E77B9880A666F68B517 /* Main.storyboard */, + ); + path = KonanTestLauncher; + sourceTree = ""; + }; + 65222469E9E3E627366829E9 /* src */ = { + isa = PBXGroup; + children = ( + 65222A6C6191FBB233B995FB /* KonanTestLauncherMain */, + ); + path = src; + sourceTree = ""; + }; + 652229EDCC8B5D981F51A0FE /* Products */ = { + isa = PBXGroup; + children = ( + 65222B6AE5EB6447D2D3E6ED /* KonanTestLauncher.app */, + ); + name = Products; + sourceTree = ""; + }; + 65222A6C6191FBB233B995FB /* KonanTestLauncherMain */ = { + isa = PBXGroup; + children = ( + 65222B5F2F23FCBC77FE9C46 /* kotlin */, + ); + path = KonanTestLauncherMain; + sourceTree = ""; + }; + 65222B5F2F23FCBC77FE9C46 /* kotlin */ = { + isa = PBXGroup; + children = ( + ); + path = kotlin; + sourceTree = ""; + }; + 65222C68A5CC689D5AA66D3B = { + isa = PBXGroup; + children = ( + 652229EDCC8B5D981F51A0FE /* Products */, + 6522238C8FBBB7F836D071CF /* KonanTestLauncher */, + ); + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 65222A26710249E058596147 /* KonanTestLauncher */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6522285AF364E21A1540BD08 /* Build configuration list for PBXNativeTarget "KonanTestLauncher" */; + buildPhases = ( + 652225D77218699A3A7F6722 /* Sources */, + 65222DAFA15A9AB521E55317 /* Frameworks */, + 65222B3A6677E222C51FAFB8 /* Resources */, + 65222EB12A851E15D756D3F8 /* Compile Kotlin/Native */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = KonanTestLauncher; + productName = KonanTestLauncher; + productReference = 65222B6AE5EB6447D2D3E6ED /* KonanTestLauncher.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 65222D052D6D41A4EEFB4821 /* Project object */ = { + isa = PBXProject; + attributes = { + CLASSPREFIX = ""; + ORGANIZATIONNAME = jetbrains; + TargetAttributes = { + 65222A26710249E058596147 = { + DevelopmentTeam = ""; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 6522294C3F904F562F8F0628 /* Build configuration list for PBXProject "KonanTestLauncher" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + English, + en, + Base, + ); + mainGroup = 65222C68A5CC689D5AA66D3B; + productRefGroup = 652229EDCC8B5D981F51A0FE /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 65222A26710249E058596147 /* KonanTestLauncher */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 65222B3A6677E222C51FAFB8 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 652225006B71BB79B6D0D726 /* Assets.xcassets in Resources */, + 65222623D2703AEFB363121F /* LaunchScreen.storyboard in Resources */, + 6522238E98511C4FCE1C84DB /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 65222EB12A851E15D756D3F8 /* Compile Kotlin/Native */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Compile Kotlin/Native"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/bash; + shellScript = "set -x\n\ncp \"$PROJECT_DIR/KonanTestLauncher/build/$TARGET_NAME.kexe\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 652225D77218699A3A7F6722 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 6522214B5DB6829939E3F325 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 652225210978EC9016CB5EFF /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; + 65222E77B9880A666F68B517 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 6522292479593142D11AD88A /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 65222812DCD81B77CE1F15D9 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 65222AF1F63AE763ED0326E5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = KonanTestLauncher/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.jetbrains.kotlin.KonanTestLauncher; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = arm64; + }; + name = Debug; + }; + 65222BB9FE8C772F3939AE5A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.4; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 65222EF46F1047DBF88CFC60 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = KonanTestLauncher/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.jetbrains.kotlin.KonanTestLauncher; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = arm64; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 6522285AF364E21A1540BD08 /* Build configuration list for PBXNativeTarget "KonanTestLauncher" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 65222AF1F63AE763ED0326E5 /* Debug */, + 65222EF46F1047DBF88CFC60 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6522294C3F904F562F8F0628 /* Build configuration list for PBXProject "KonanTestLauncher" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 65222BB9FE8C772F3939AE5A /* Debug */, + 65222812DCD81B77CE1F15D9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 65222D052D6D41A4EEFB4821 /* Project object */; +} diff --git a/backend.native/tests/iosLauncher/KonanTestLauncher.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/backend.native/tests/iosLauncher/KonanTestLauncher.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000000..919434a6254 --- /dev/null +++ b/backend.native/tests/iosLauncher/KonanTestLauncher.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/backend.native/tests/iosLauncher/KonanTestLauncher.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/backend.native/tests/iosLauncher/KonanTestLauncher.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000000..18d981003d6 --- /dev/null +++ b/backend.native/tests/iosLauncher/KonanTestLauncher.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/backend.native/tests/iosLauncher/KonanTestLauncher.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/backend.native/tests/iosLauncher/KonanTestLauncher.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000000..5f88f383a29 --- /dev/null +++ b/backend.native/tests/iosLauncher/KonanTestLauncher.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,10 @@ + + + + + BuildSystemType + Original + PreviewsEnabled + + + \ No newline at end of file diff --git a/backend.native/tests/iosLauncher/KonanTestLauncher/Assets.xcassets/AppIcon.appiconset/Contents.json b/backend.native/tests/iosLauncher/KonanTestLauncher/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000000..d8db8d65fd7 --- /dev/null +++ b/backend.native/tests/iosLauncher/KonanTestLauncher/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/backend.native/tests/iosLauncher/KonanTestLauncher/Base.lproj/LaunchScreen.storyboard b/backend.native/tests/iosLauncher/KonanTestLauncher/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000000..f83f6fd5810 --- /dev/null +++ b/backend.native/tests/iosLauncher/KonanTestLauncher/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend.native/tests/iosLauncher/KonanTestLauncher/Base.lproj/Main.storyboard b/backend.native/tests/iosLauncher/KonanTestLauncher/Base.lproj/Main.storyboard new file mode 100644 index 00000000000..b52868927a9 --- /dev/null +++ b/backend.native/tests/iosLauncher/KonanTestLauncher/Base.lproj/Main.storyboard @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend.native/tests/iosLauncher/KonanTestLauncher/Info.plist b/backend.native/tests/iosLauncher/KonanTestLauncher/Info.plist new file mode 100644 index 00000000000..16be3b68112 --- /dev/null +++ b/backend.native/tests/iosLauncher/KonanTestLauncher/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/build-tools/src/main/kotlin/org/jetbrains/kotlin/ExecutorService.kt b/build-tools/src/main/kotlin/org/jetbrains/kotlin/ExecutorService.kt index 947c16cad8a..7f22684c42c 100644 --- a/build-tools/src/main/kotlin/org/jetbrains/kotlin/ExecutorService.kt +++ b/build-tools/src/main/kotlin/org/jetbrains/kotlin/ExecutorService.kt @@ -17,6 +17,8 @@ package org.jetbrains.kotlin import groovy.lang.Closure +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json import org.gradle.api.Action import org.gradle.api.Project import org.gradle.process.ExecResult @@ -26,14 +28,15 @@ import org.jetbrains.kotlin.konan.target.Architecture import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.Xcode +import java.io.* -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.io.File +import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths +import java.nio.file.StandardCopyOption import java.time.LocalDateTime import java.time.format.DateTimeFormatter +import java.util.concurrent.TimeUnit /** * A replacement of the standard `exec {}` @@ -77,10 +80,10 @@ fun create(project: Project): ExecutorService { val exe = executable executable = absoluteQemu args = listOf("-L", absoluteTargetSysRoot, - // This is to workaround an endianess issue. - // See https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=731082 for details. - "$absoluteTargetSysRoot/lib/ld.so.1", "--inhibit-cache", - exe) + args + // This is to workaround an endianess issue. + // See https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=731082 for details. + "$absoluteTargetSysRoot/lib/ld.so.1", "--inhibit-cache", + exe) + args } } } @@ -90,11 +93,12 @@ fun create(project: Project): ExecutorService { KonanTarget.WATCHOS_X86, KonanTarget.WATCHOS_X64 -> simulator(project) + KonanTarget.IOS_ARM32, + KonanTarget.IOS_ARM64 -> deviceLauncher(project) + else -> { if (project.hasProperty("remote")) sshExecutor(project) - else object : ExecutorService { - override fun execute(action: Action): ExecResult? = project.exec(action) - } + else localExecutorService(project) } } } @@ -109,7 +113,7 @@ data class ProcessOutput(var stdOut: String, var stdErr: String, var exitCode: I * @param args arguments for a process */ fun runProcess(executor: (Action) -> ExecResult?, - executable: String, args: List) : ProcessOutput { + executable: String, args: List): ProcessOutput { val outStream = ByteArrayOutputStream() val errStream = ByteArrayOutputStream() @@ -141,7 +145,7 @@ fun runProcess(executor: (Action) -> ExecResult?, * @param input an input string to be passed through the standard input stream */ fun runProcessWithInput(executor: (Action) -> ExecResult?, - executable: String, args: List, input: String) : ProcessOutput { + executable: String, args: List, input: String): ProcessOutput { val outStream = ByteArrayOutputStream() val errStream = ByteArrayOutputStream() val inStream = ByteArrayInputStream(input.toByteArray()) @@ -168,7 +172,8 @@ fun runProcessWithInput(executor: (Action) -> ExecResult?, * @throws IllegalStateException if there are no executor in the project. */ val Project.executor: ExecutorService - get() = this.convention.plugins["executor"] as? ExecutorService ?: throw IllegalStateException("Executor wasn't found") + get() = this.convention.plugins["executor"] as? ExecutorService + ?: throw IllegalStateException("Executor wasn't found") /** * Creates a new executor service with additional action [actionParameter] executed after the main one. @@ -198,7 +203,7 @@ fun Project.executeAndCheck(executable: Path, arguments: List = emptyLis |stdout: $stdOut |stderr: $stdErr """.trimMargin()) - check(exitCode == 0) { "Execution failed with exit code: $exitCode "} + check(exitCode == 0) { "Execution failed with exit code: $exitCode" } } /** @@ -207,6 +212,10 @@ fun Project.executeAndCheck(executable: Path, arguments: List = emptyLis */ fun localExecutor(project: Project) = { a: Action -> project.exec(a) } +fun localExecutorService(project: Project): ExecutorService = object : ExecutorService { + override fun execute(action: Action): ExecResult? = project.exec(action) +} + /** * Executes a given action with iPhone Simulator. * @@ -215,7 +224,7 @@ fun localExecutor(project: Project) = { a: Action -> project.exec(a * @param iosDevice an optional project property used to control simulator's device type * Specify -PiosDevice=iPhone X to set it */ -private fun simulator(project: Project) : ExecutorService = object : ExecutorService { +private fun simulator(project: Project): ExecutorService = object : ExecutorService { private val target = project.testTarget @@ -263,7 +272,7 @@ private fun simulator(project: Project) : ExecutorService = object : ExecutorSer * @param remote makes binaries be executed on a remote host * Specify it as -Premote=user@host */ -private fun sshExecutor(project: Project) : ExecutorService = object : ExecutorService { +private fun sshExecutor(project: Project): ExecutorService = object : ExecutorService { private val remote: String = project.property("remote").toString() private val sshArgs: List = System.getenv("SSH_ARGS")?.split(" ") ?: emptyList() @@ -311,3 +320,231 @@ private fun sshExecutor(project: Project) : ExecutorService = object : ExecutorS } } } + +private fun deviceLauncher(project: Project) = object : ExecutorService { + private val xcProject = Paths.get(project.testOutputRoot, "launcher") + + private val idb = project.findProperty("idb_path") as? String ?: "idb" + + private val deviceName = project.findProperty("device_name") as? String + + override fun execute(action: Action): ExecResult? { + val udid = targetUDID() + println("Found device UDID: $udid") + install(udid, xcProject.resolve("build/KonanTestLauncher.ipa").toString()) + val bundleId = "org.jetbrains.kotlin.KonanTestLauncher" + val commands = startDebugServer(udid, bundleId) + .split("\n") + .filter { it.isNotBlank() } + .flatMap { listOf("-o", it) } + + var savedOut: OutputStream? = null + val out = ByteArrayOutputStream() + val result = project.exec { execSpec: ExecSpec -> + action.execute(execSpec) + execSpec.executable = "lldb" + execSpec.args = commands + "-b" + "-o" + "command script import ${pythonScript()}" + + "-o" + ("process launch" + + (execSpec.args.takeUnless { it.isEmpty() } + ?.let { " -- ${it.joinToString(" ")}" } + ?: "")) + + "-o" + "get_exit_code" + + "-k" + "get_exit_code" + + "-k" + "exit -1" + // A test task that uses project.exec { } sets the stdOut to parse the result, + // but the test executable is being run under debugger that has its own output mixed with the + // output from the test. Save the stdOut from the test to write the parsed output to it. + savedOut = execSpec.standardOutput + execSpec.standardOutput = out + } + out.toString() + .also { if (project.verboseTest) println(it) } + .split("\n") + .dropWhile { s -> !s.startsWith("(lldb) process launch") } + .drop(1) // drop 'process launch' also + .dropLastWhile { ! it.matches(".*Process [0-9]* exited with status .*".toRegex()) } + .joinToString("\n") { + it.replace("Process [0-9]* exited with status .*".toRegex(), "") + .replace("\r", "") // TODO: investigate: where does the \r comes from + } + .also { + savedOut?.write(it.toByteArray()) + } + + uninstall(udid, bundleId) + kill() + return result + } + + /* + * This script kills the target process in case it has been stopped, + * and exists lldb with the same exit code as a target process. + */ + private fun pythonScript(): String = xcProject.resolve("lldb_cmd.py").toFile().run { + writeText( // language=Python + """ + import lldb + + def exit_code(debugger, command, exe_ctx, result, internal_dict): + process = exe_ctx.GetProcess() + state = process.GetState() + if state == lldb.eStateStopped: + debugger.HandleCommand("bt all") + process.Kill() + code = process.GetExitStatus() + debugger.HandleCommand("exit %d" % code) + + def __lldb_init_module(debugger, _): + debugger.HandleCommand('command script add -f lldb_cmd.exit_code get_exit_code') + """.trimIndent()) + absolutePath + } + + private fun kill() = project.exec { + it.commandLine(idb, "kill") + } + + private fun targetUDID(): String { + val out = ByteArrayOutputStream() + // idb launches idb_companion but doesn't wait for it and just exits. + // So relaunch `list-targets` again. + for (i in 1..3) { + project.exec { + it.commandLine(idb, "list-targets", "--json") + it.standardOutput = out + }.assertNormalExitValue() + if (out.toString().trim().isNotEmpty()) break + else TimeUnit.SECONDS.sleep(i.toLong()) + } + return out.toString().run { + check(isNotEmpty()) + @Serializable + data class DeviceTarget(val name: String, val udid: String, val state: String, val type: String) + split("\n") + .filter { it.isNotEmpty() } + .map { Json(strictMode = false).parse(DeviceTarget.serializer(), it) } + .first { + it.type == "device" && deviceName?.run { this == it.name } ?: true + } + .udid + } + } + + private fun install(udid: String, bundlePath: String) { + val out = ByteArrayOutputStream() + + val result = project.exec { + it.workingDir = xcProject.toFile() + it.commandLine = listOf(idb, "install", "--udid", udid, bundlePath) + it.standardOutput = out + it.errorOutput = out + it.isIgnoreExitValue = true + } + println(out.toString()) + check(result.exitValue == 0) { "Installation of $bundlePath failed: $out" } + } + + private fun uninstall(udid: String, bundleId: String) { + val out = ByteArrayOutputStream() + + project.exec { + it.workingDir = xcProject.toFile() + it.commandLine = listOf(idb, "uninstall", "--udid", udid, bundleId) + it.standardOutput = out + it.errorOutput = out + it.isIgnoreExitValue = true + } + println(out.toString()) + } + + private fun startDebugServer(udid: String, bundleId: String): String { + val out = ByteArrayOutputStream() + + val result = project.exec { + it.workingDir = xcProject.toFile() + it.commandLine = listOf(idb, "debugserver", "start", "--udid", udid, bundleId) + it.standardOutput = out + it.errorOutput = out + it.isIgnoreExitValue = true + } + check(result.exitValue == 0) { "Failed to start debug server: $out" } + return out.toString() + } +} + +fun KonanTestExecutable.configureXcodeBuild() { + this.doBeforeRun = Action { + val signIdentity = project.findProperty("sign_identity") as? String ?: "iPhone Developer" + val developmentTeam = project.findProperty("development_team") as? String + requireNotNull(developmentTeam) { "Specify '-Pdevelopment_team=' with the your team id" } + val xcProject = Paths.get(project.testOutputRoot, "launcher") + + val shellScript: String = // language=Bash + mutableListOf(""" + set -x + # Copy executable to the build dir. + COPY_TO="${"$"}TARGET_BUILD_DIR/${"$"}EXECUTABLE_PATH" + cp "${project.file(executable).absolutePath}" "${"$"}COPY_TO" + # copy dSYM if it exists + DSYM_DIR="${project.file("$executable.dSYM").absolutePath}" + if [ -d "${"$"}DSYM_DIR" ]; then + cp -r "${"$"}DSYM_DIR" "${"$"}TARGET_BUILD_DIR/${"$"}EXECUTABLE_FOLDER_PATH/" + fi + """.trimIndent()).also { + if (this is FrameworkTest) { + // Create a Frameworks folder inside the build dir. + it += "mkdir -p \"\$TARGET_BUILD_DIR/\$FRAMEWORKS_FOLDER_PATH\"" + // Copy each framework to the Frameworks dir. + it += frameworkNames.map { name -> + "cp -r \"$testOutput/$testName/${project.testTarget.name}/$name.framework\" " + + "\"\$TARGET_BUILD_DIR/\$FRAMEWORKS_FOLDER_PATH/$name.framework\"" + } + } + }.joinToString(separator = "\\n") { it.replace("\"", "\\\"") } + + // Copy template xcode project. + project.file("iosLauncher").copyRecursively(xcProject.toFile(), overwrite = true) + + xcProject.resolve("KonanTestLauncher.xcodeproj/project.pbxproj") + .toFile() + .apply { + val text = readLines().joinToString("\n") { + when { + it.contains("CODE_SIGN_IDENTITY") -> + it.replaceAfter("= ", "\"$signIdentity\";") + it.contains("DEVELOPMENT_TEAM") || it.contains("DevelopmentTeam") -> + it.replaceAfter("= ", "$developmentTeam;") + it.contains("shellScript = ") -> + it.replaceAfter("= ", "\"$shellScript\";") + else -> it + } + } + writeText(text) + } + + val sdk = when (project.testTarget) { + KonanTarget.IOS_ARM32, KonanTarget.IOS_ARM64 -> Xcode.current.iphoneosSdk + else -> error("Unsupported target: ${project.testTarget}") + } + + fun xcodebuild(vararg elements: String) { + val xcode = listOf("/usr/bin/xcrun", "-sdk", sdk, "xcodebuild") + val out = ByteArrayOutputStream() + val result = project.exec { + it.workingDir = xcProject.toFile() + it.commandLine = xcode + elements.toList() + it.standardOutput = out + } + println(out.toString("UTF-8")) + result.assertNormalExitValue() + } + xcodebuild("-workspace", "KonanTestLauncher.xcodeproj/project.xcworkspace", + "-scheme", "KonanTestLauncher", "-allowProvisioningUpdates", "-destination", + "generic/platform=iOS", "build") + val archive = xcProject.resolve("build/KonanTestLauncher.xcarchive").toString() + xcodebuild("-workspace", "KonanTestLauncher.xcodeproj/project.xcworkspace", + "-scheme", "KonanTestLauncher", "archive", "-archivePath", archive) + xcodebuild("-exportArchive", "-archivePath", archive, "-exportOptionsPlist", "KonanTestLauncher/Info.plist", + "-exportPath", xcProject.resolve("build").toString()) + } +} diff --git a/build-tools/src/main/kotlin/org/jetbrains/kotlin/FrameworkTest.kt b/build-tools/src/main/kotlin/org/jetbrains/kotlin/FrameworkTest.kt index be37ee9c957..85727942ddd 100644 --- a/build-tools/src/main/kotlin/org/jetbrains/kotlin/FrameworkTest.kt +++ b/build-tools/src/main/kotlin/org/jetbrains/kotlin/FrameworkTest.kt @@ -22,7 +22,7 @@ import java.nio.file.Paths * @property testName test name * @property frameworkNames names of frameworks */ -open class FrameworkTest : DefaultTask() { +open class FrameworkTest : DefaultTask(), KonanTestExecutable { @Input lateinit var swiftSources: List @@ -35,7 +35,17 @@ open class FrameworkTest : DefaultTask() { @Input var fullBitcode: Boolean = false - private val testOutput: String = project.testOutputFramework + val testOutput: String = project.testOutputFramework + + override val executable: String + get() { + check(::testName.isInitialized) { "Test name should be set" } + return Paths.get(testOutput, testName, "swiftTestExecutable").toString() + } + + override var doBeforeRun: Action? = null + + override var doBeforeBuild: Action? = null override fun configure(config: Closure<*>): Task { super.configure(config) @@ -48,15 +58,18 @@ open class FrameworkTest : DefaultTask() { check(::testName.isInitialized) { "Test name should be set" } check(::frameworkNames.isInitialized) { "Framework names should be set" } frameworkNames.forEach { frameworkName -> - dependsOn(project.tasks.getByName("compileKonan$frameworkName")) + val compileTask = project.tasks.getByName("compileKonan$frameworkName") + doBeforeBuild?.let { compileTask.doFirst(it) } + dependsOn(compileTask) } + // Build test executable as a first action of the task before executing the test + this.doFirst { buildTestExecutable() } return this } private fun setRootDependency(vararg s: String) = s.forEach { dependsOn(project.rootProject.tasks.getByName(it)) } - @TaskAction - fun run() { + private fun buildTestExecutable() { val frameworkParentDirPath = "$testOutput/$testName/${project.testTarget.name}" frameworkNames.forEach { frameworkName -> val frameworkPath = "$frameworkParentDirPath/$frameworkName.framework" @@ -85,14 +98,18 @@ open class FrameworkTest : DefaultTask() { listOf(provider.toString(), swiftMain) val options = listOf( "-g", + "-Xlinker", "-rpath", "-Xlinker", "@executable_path/Frameworks", "-Xlinker", "-rpath", "-Xlinker", frameworkParentDirPath, "-F", frameworkParentDirPath, "-Xcc", "-Werror" // To fail compilation on warnings in framework header. ) - val testExecutable = Paths.get(testOutput, testName, "swiftTestExecutable") - compileSwift(project, project.testTarget, sources, options, testExecutable, fullBitcode) + compileSwift(project, project.testTarget, sources, options, Paths.get(executable), fullBitcode) + } - runTest(testExecutable) + @TaskAction + fun run() { + doBeforeRun?.execute(this) + runTest(executorService = project.executor, testExecutable = Paths.get(executable)) } /** @@ -143,11 +160,9 @@ open class FrameworkTest : DefaultTask() { ) } - private fun runTest(testExecutable: Path, args: List = emptyList()) { - val executor = (project.convention.plugins["executor"] as? ExecutorService) - ?: throw RuntimeException("Executor wasn't found") + private fun runTest(executorService: ExecutorService, testExecutable: Path, args: List = emptyList()) { val (stdOut, stdErr, exitCode) = runProcess( - executor = executor.add(Action { it.environment = buildEnvironment() })::execute, + executor = executorService.add(Action { it.environment = buildEnvironment() })::execute, executable = testExecutable.toString(), args = args) @@ -184,6 +199,7 @@ open class FrameworkTest : DefaultTask() { else -> error("Cannot validate bitcode for test target $testTarget") } - runTest(bitcodeBuildTool, args = listOf("--sdk", sdk, "-v", "-t", toolPath, frameworkBinary)) + runTest(executorService = localExecutorService(project), + testExecutable = bitcodeBuildTool, args = listOf("--sdk", sdk, "-v", "-t", toolPath, frameworkBinary)) } } \ No newline at end of file diff --git a/build-tools/src/main/kotlin/org/jetbrains/kotlin/KonanTestExecutable.kt b/build-tools/src/main/kotlin/org/jetbrains/kotlin/KonanTestExecutable.kt new file mode 100644 index 00000000000..5e1a5cfae24 --- /dev/null +++ b/build-tools/src/main/kotlin/org/jetbrains/kotlin/KonanTestExecutable.kt @@ -0,0 +1,28 @@ +package org.jetbrains.kotlin + +import org.gradle.api.Action +import org.gradle.api.Task + +/** + * An interface that any test that works with ExecutorService + * should implement to be run on different platforms. + */ +interface KonanTestExecutable : Task { + /** + * Test executable to be run by the service. + */ + val executable: String + + /** + * Action that configures task or does some workload before the test will be executed. + * Could be done as a first step in the test or just as a `doFirst` action in the test task. + */ + var doBeforeRun: Action? + + /** + * Action that configures task or does some workload before the test will be built. + * Depending on the test task implementation this action is done before the build task + * or as its `doFirst` action. + */ + var doBeforeBuild: Action? +} \ No newline at end of file diff --git a/build-tools/src/main/kotlin/org/jetbrains/kotlin/KotlinNativeTest.kt b/build-tools/src/main/kotlin/org/jetbrains/kotlin/KotlinNativeTest.kt index 8eabef6a6b6..aa90d3683ce 100644 --- a/build-tools/src/main/kotlin/org/jetbrains/kotlin/KotlinNativeTest.kt +++ b/build-tools/src/main/kotlin/org/jetbrains/kotlin/KotlinNativeTest.kt @@ -21,7 +21,7 @@ import java.util.regex.Pattern import org.jetbrains.kotlin.konan.target.HostManager -abstract class KonanTest : DefaultTask() { +abstract class KonanTest : DefaultTask(), KonanTestExecutable { enum class Logger { EMPTY, // Built without test runner GTEST, // Google test log output @@ -53,7 +53,7 @@ abstract class KonanTest : DefaultTask() { /** * Test executable. */ - abstract val executable: String + abstract override val executable: String /** * Test source. @@ -72,7 +72,10 @@ abstract class KonanTest : DefaultTask() { * should be done before the build and not run. */ @Input @Optional - var doBefore: Action? = null + override var doBeforeBuild: Action? = null + + @Input @Optional + override var doBeforeRun: Action? = null @Suppress("UnstableApiUsage") override fun configure(config: Closure<*>): Task { @@ -125,7 +128,7 @@ fun Project.createTest(name: String, type: Class, config: Clos compileTask.sameDependenciesAs(this) // Run task should depend on compile task this.dependsOn(compileTask) - if (doBefore != null) compileTask.doFirst(doBefore!!) + doBeforeBuild?.let { compileTask.doFirst(it) } compileTask.enabled = enabled } } @@ -146,20 +149,23 @@ open class KonanGTest : KonanTest() { var statistics = Statistics() @TaskAction - override fun run() = runProcess( - executor = project.executor::execute, - executable = executable, - args = arguments - ).run { - parse(stdOut) - println(""" + override fun run() { + doBeforeRun?.execute(this) + runProcess( + executor = project.executor::execute, + executable = executable, + args = arguments + ).run { + parse(stdOut) + println(""" |stdout: |$stdOut |stderr: |$stdErr |exit code: $exitCode """.trimMargin()) - check(exitCode == 0) { "Test $executable exited with $exitCode" } + check(exitCode == 0) { "Test $executable exited with $exitCode" } + } } private fun parse(output: String) = statistics.apply { @@ -230,6 +236,7 @@ open class KonanLocalTest : KonanTest() { @TaskAction override fun run() { + doBeforeRun?.execute(this) val times = if (multiRuns && multiArguments != null) multiArguments!!.size else 1 var output = ProcessOutput("", "", 0) for (i in 1..times) { @@ -333,16 +340,11 @@ open class KonanStandaloneTest : KonanLocalTest() { open class KonanDriverTest : KonanStandaloneTest() { override fun configure(config: Closure<*>): Task { super.configure(config) - doBefore?.let { doFirst(it) } + doFirst { konan() } + doBeforeBuild?.let { doFirst(it) } return this } - @TaskAction - override fun run() { - konan() - super.run() - } - private fun konan() { val dist = project.kotlinNativeDist val konancDriver = if (HostManager.hostIsMingw) "konanc.bat" else "konanc" @@ -391,22 +393,22 @@ open class KonanLinkTest : KonanStandaloneTest() { * It will be replaced then by the actual library name. */ open class KonanDynamicTest : KonanStandaloneTest() { + override fun configure(config: Closure<*>): Task { + super.configure(config) + doFirst { clang() } + return this + } + /** * File path to the C source. */ @Input lateinit var cSource: String - @TaskAction - override fun run() { - clang() - super.run() - } - // Replace testlib_api.h and all occurrences of the testlib with the actual name of the test private fun processCSource(): String { val sourceFile = File(cSource) - val prefixedName = if (HostManager.hostIsMingw) "$name" else "lib$name" + val prefixedName = if (HostManager.hostIsMingw) name else "lib$name" val res = sourceFile.readText() .replace("#include \"testlib_api.h\"", "#include \"${prefixedName}_api.h\"") .replace("testlib", prefixedName) diff --git a/build-tools/src/main/kotlin/org/jetbrains/kotlin/Utils.kt b/build-tools/src/main/kotlin/org/jetbrains/kotlin/Utils.kt index e183f89b708..45507d13597 100644 --- a/build-tools/src/main/kotlin/org/jetbrains/kotlin/Utils.kt +++ b/build-tools/src/main/kotlin/org/jetbrains/kotlin/Utils.kt @@ -29,6 +29,9 @@ val Project.testTarget val Project.verboseTest get() = hasProperty("test_verbose") +val Project.testOutputRoot + get() = findProperty("testOutputRoot") as String + val Project.testOutputLocal get() = (findProperty("testOutputLocal") as File).toString() @@ -218,9 +221,9 @@ fun compileSwift(project: Project, target: KonanTarget, sources: List, o val swiftTarget = when (target) { KonanTarget.IOS_X64 -> "x86_64-apple-ios" + configs.osVersionMin - KonanTarget.IOS_ARM64 -> "arm64_64-apple-ios" + configs.osVersionMin + KonanTarget.IOS_ARM64 -> "arm64-apple-ios" + configs.osVersionMin KonanTarget.TVOS_X64 -> "x86_64-apple-tvos" + configs.osVersionMin - KonanTarget.TVOS_ARM64 -> "arm64_64-apple-tvos" + configs.osVersionMin + KonanTarget.TVOS_ARM64 -> "arm64-apple-tvos" + configs.osVersionMin KonanTarget.MACOS_X64 -> "x86_64-apple-macosx" + configs.osVersionMin KonanTarget.WATCHOS_X86 -> "i386-apple-watchos" + configs.osVersionMin KonanTarget.WATCHOS_X64 -> "x86_64-apple-watchos" + configs.osVersionMin