diff --git a/build-tools/build.gradle.kts b/build-tools/build.gradle.kts index 1b78f7ea079..84c4df40623 100644 --- a/build-tools/build.gradle.kts +++ b/build-tools/build.gradle.kts @@ -103,6 +103,7 @@ val compileGroovy: GroovyCompile by tasks // https://youtrack.jetbrains.com/issue/KT-37435 compileKotlin.apply { + kotlinOptions.jvmTarget = "1.8" kotlinOptions.freeCompilerArgs += listOf("-Xno-optimized-callable-references", "-Xskip-prerelease-check") } diff --git a/build-tools/src/main/kotlin/org/jetbrains/kotlin/testing/native/NativeTest.kt b/build-tools/src/main/kotlin/org/jetbrains/kotlin/testing/native/NativeTest.kt new file mode 100644 index 00000000000..da56dd67c38 --- /dev/null +++ b/build-tools/src/main/kotlin/org/jetbrains/kotlin/testing/native/NativeTest.kt @@ -0,0 +1,193 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +package org.jetbrains.kotlin.testing.native + +import groovy.lang.Closure +import java.io.File +import javax.inject.Inject +import org.gradle.api.* +import org.gradle.api.tasks.* +import org.jetbrains.kotlin.ExecClang +import org.jetbrains.kotlin.bitcode.CompileToBitcode +import org.jetbrains.kotlin.konan.target.* + +open class CompileNativeTest @Inject constructor( + @InputFile val inputFile: File, + @Input val target: String +) : DefaultTask () { + @OutputFile + var outputFile = project.buildDir.resolve("bin/test/$target/${inputFile.nameWithoutExtension}.o") + + @Input + val clangArgs = mutableListOf() + + @TaskAction + fun compile() { + val plugin = project.convention.getPlugin(ExecClang::class.java) + plugin.execBareClang(Action { + it.executable = "clang++" + it.args = clangArgs + listOf(inputFile.absolutePath, "-o", outputFile.absolutePath) + }) + } +} + +open class LinkNativeTest @Inject constructor( + @InputFiles val inputFiles: List, + @OutputFile val outputFile: File, + @Internal val target: String, + @Internal val linkerArgs: List, + private val platformManager: PlatformManager +) : DefaultTask () { + companion object { + fun create( + project: Project, + platformManager: PlatformManager, + taskName: String, + inputFiles: List, + target: String, + outputFile: File, + linkerArgs: List + ): LinkNativeTest = project.tasks.create( + taskName, + LinkNativeTest::class.java, + inputFiles, + outputFile, + target, + linkerArgs, + platformManager) + + fun create( + project: Project, + platformManager: PlatformManager, + taskName: String, + inputFiles: List, + target: String, + executableName: String, + linkerArgs: List = listOf() + ): LinkNativeTest = create( + project, + platformManager, + taskName, + inputFiles, + target, + project.buildDir.resolve("bin/test/$target/$executableName"), + linkerArgs) + } + + @get:Input + val commands: List> + get() { + // Getting link commands requires presence of a target toolchain. + // Thus we cannot get them at the configuration stage because the toolchain may be not downloaded yet. + val linker = platformManager.platform(platformManager.targetByName(target)).linker + return linker.finalLinkCommands( + inputFiles.map { it.absolutePath }, + outputFile.absolutePath, + listOf(), + linkerArgs, + optimize = false, + debug = false, + kind = LinkerOutputKind.EXECUTABLE, + outputDsymBundle = "", + needsProfileLibrary = false + ).map { it.argsWithExecutable } + } + + @TaskAction + fun link() { + for (command in commands) { + project.exec { + it.commandLine(command) + } + } + } +} + +@Suppress("UNCHECKED_CAST") +private fun T.configure(f: T.() -> Unit): T = + this.configure(object: Closure(this) { + // Dynamically invoked by Groovy + fun doCall() { + f() + } + }) as T + +fun createTestTask( + project: Project, + testTaskName: String, + testedTaskNames: List +): Task { + val platformManager = project.rootProject.findProperty("platformManager") as PlatformManager + val googleTestExtension = project.extensions.getByName(RuntimeTestingPlugin.GOOGLE_TEST_EXTENSION_NAME) as GoogleTestExtension + val testedTasks = testedTaskNames.map { + project.tasks.getByName(it) as CompileToBitcode + } + val target = testedTasks.map { + it.target + }.distinct().single() + val konanTarget = platformManager.targetByName(target) + val compileToBitcodeTasks = testedTasks.mapNotNull { + val name = "${it.name}TestBitcode" + val task = project.tasks.findByName(name) as? CompileToBitcode ?: + project.tasks.create(name, + CompileToBitcode::class.java, + it.srcRoot, + "${it.folderName}Tests", + target, "test" + ).configure { + excludeFiles = emptyList() + includeFiles = listOf("**/*Test.cpp", "**/*Test.mm") + dependsOn(it) + compilerArgs.addAll(it.compilerArgs) + headersDirs += googleTestExtension.headersDirs + } + if (task.inputFiles.count() == 0) + null + else + task + } + val testFrameworkTasks = listOf( + project.tasks.getByName("${target}Googletest") as CompileToBitcode, + project.tasks.getByName("${target}Googlemock") as CompileToBitcode + ) + val compileToObjectFileTasks = (compileToBitcodeTasks + testedTasks + testFrameworkTasks).map { + val name = "${it.name}Object" + val clangFlags = platformManager.platform(konanTarget).configurables as ClangFlags + project.tasks.findByName(name) as? CompileNativeTest ?: + project.tasks.create(name, + CompileNativeTest::class.java, + it.outFile, + target + ).configure { + dependsOn(it) + clangArgs.addAll(clangFlags.clangFlags) + clangArgs.addAll(clangFlags.clangNooptFlags) + } + } + val linkTask = LinkNativeTest.create( + project, + platformManager, + "${testTaskName}Link", + compileToObjectFileTasks.map { it.outputFile }, + target, + testTaskName + ).configure { + dependsOn(compileToObjectFileTasks) + } + + return project.tasks.create(testTaskName, Exec::class.java).apply { + dependsOn(linkTask) + + workingDir = project.buildDir.resolve("testReports/$testTaskName") + val xmlReport = workingDir.resolve("report.xml") + executable(linkTask.outputFile) + args("--gtest_output=xml:${xmlReport.absoluteFile}") + + doFirst { + workingDir.mkdirs() + } + } +} diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index af742d9977a..1afc954d167 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -8,10 +8,14 @@ set(CMAKE_CXX_STANDARD 14) set(SRC_DIR ${CMAKE_SOURCE_DIR}/src) +set(GOOGLETEST_DIR ${CMAKE_SOURCE_DIR}/googletest) + include_directories(${SRC_DIR}/main/cpp) include_directories(${SRC_DIR}/debug/headers) include_directories(${CMAKE_SOURCE_DIR}/../common/src/hash/headers) +include_directories(${GOOGLETEST_DIR}/googletest/include) +include_directories(${GOOGLETEST_DIR}/googlemock/include) add_executable(runtime src/main/cpp/Arrays.cpp @@ -76,6 +80,12 @@ add_executable(runtime src/objc/cpp/ObjCExportCollections.mm src/objc/cpp/ObjCInteropUtilsClasses.mm + # Tests + src/test_support/cpp/CompilerGenerated.cpp + src/test_support/cpp/CompilerGeneratedObjC.mm + src/test_support/cpp/TestLauncher.cpp + + src/main/cpp/ArraysTest.cpp ) target_compile_definitions(runtime PUBLIC "-DKONAN_OSX=1") diff --git a/runtime/build.gradle.kts b/runtime/build.gradle.kts index 5385e4f7c35..469528d8be2 100644 --- a/runtime/build.gradle.kts +++ b/runtime/build.gradle.kts @@ -86,12 +86,54 @@ bitcode { create("objc") { includeRuntime() } + + create("test_support", outputGroup = "test") { + includeRuntime() + dependsOn("downloadGoogleTest") + headersDirs += googletest.headersDirs + } +} + +targetList.forEach { targetName -> + createTestTask( + project, + "${targetName}StdAllocRuntimeTests", + listOf( + "${targetName}Runtime", + "${targetName}TestSupport", + "${targetName}Strict", + "${targetName}Release", + "${targetName}StdAlloc" + ) + ) + + createTestTask( + project, + "${targetName}MimallocRuntimeTests", + listOf( + "${targetName}Runtime", + "${targetName}TestSupport", + "${targetName}Strict", + "${targetName}Release", + "${targetName}Mimalloc", + "${targetName}OptAlloc" + ) + ) + + tasks.register("${targetName}RuntimeTests") { + dependsOn("${targetName}StdAllocRuntimeTests") + dependsOn("${targetName}MimallocRuntimeTests") + } } val hostRuntime by tasks.registering { dependsOn("${hostName}Runtime") } +val hostRuntimeTests by tasks.registering { + dependsOn("${hostName}RuntimeTests") +} + val assemble by tasks.registering { dependsOn(tasks.withType(CompileToBitcode::class).matching { it.outputGroup == "main" diff --git a/runtime/src/test_support/cpp/CompilerGenerated.cpp b/runtime/src/test_support/cpp/CompilerGenerated.cpp new file mode 100644 index 00000000000..a5c51baed50 --- /dev/null +++ b/runtime/src/test_support/cpp/CompilerGenerated.cpp @@ -0,0 +1,233 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +#include "CompilerGenerated.h" +#include "Types.h" + +namespace { + +TypeInfo theAnyTypeInfoImpl = {}; +TypeInfo theArrayTypeInfoImpl = {}; +TypeInfo theBooleanArrayTypeInfoImpl = {}; +TypeInfo theByteArrayTypeInfoImpl = {}; +TypeInfo theCharArrayTypeInfoImpl = {}; +TypeInfo theDoubleArrayTypeInfoImpl = {}; +TypeInfo theFloatArrayTypeInfoImpl = {}; +TypeInfo theForeignObjCObjectTypeInfoImpl = {}; +TypeInfo theFreezableAtomicReferenceTypeInfoImpl = {}; +TypeInfo theIntArrayTypeInfoImpl = {}; +TypeInfo theLongArrayTypeInfoImpl = {}; +TypeInfo theNativePtrArrayTypeInfoImpl = {}; +TypeInfo theObjCObjectWrapperTypeInfoImpl = {}; +TypeInfo theOpaqueFunctionTypeInfoImpl = {}; +TypeInfo theShortArrayTypeInfoImpl = {}; +TypeInfo theStringTypeInfoImpl = {}; +TypeInfo theThrowableTypeInfoImpl = {}; +TypeInfo theUnitTypeInfoImpl = {}; +TypeInfo theWorkerBoundReferenceTypeInfoImpl = {}; + +ArrayHeader theEmptyStringImpl = { &theStringTypeInfoImpl, /* element count */ 0 }; + +template +struct KBox { + ObjHeader header; + const T value; +}; + +} // namespace + +extern "C" { + +extern const int KonanNeedDebugInfo = 0; + +extern const TypeInfo* theAnyTypeInfo = &theAnyTypeInfoImpl; +extern const TypeInfo* theArrayTypeInfo = &theArrayTypeInfoImpl; +extern const TypeInfo* theBooleanArrayTypeInfo = &theBooleanArrayTypeInfoImpl; +extern const TypeInfo* theByteArrayTypeInfo = &theByteArrayTypeInfoImpl; +extern const TypeInfo* theCharArrayTypeInfo = &theCharArrayTypeInfoImpl; +extern const TypeInfo* theDoubleArrayTypeInfo = &theDoubleArrayTypeInfoImpl; +extern const TypeInfo* theFloatArrayTypeInfo = &theFloatArrayTypeInfoImpl; +extern const TypeInfo* theForeignObjCObjectTypeInfo = &theForeignObjCObjectTypeInfoImpl; +extern const TypeInfo* theFreezableAtomicReferenceTypeInfo = &theFreezableAtomicReferenceTypeInfoImpl; +extern const TypeInfo* theIntArrayTypeInfo = &theIntArrayTypeInfoImpl; +extern const TypeInfo* theLongArrayTypeInfo = &theLongArrayTypeInfoImpl; +extern const TypeInfo* theNativePtrArrayTypeInfo = &theNativePtrArrayTypeInfoImpl; +extern const TypeInfo* theObjCObjectWrapperTypeInfo = &theObjCObjectWrapperTypeInfoImpl; +extern const TypeInfo* theOpaqueFunctionTypeInfo = &theOpaqueFunctionTypeInfoImpl; +extern const TypeInfo* theShortArrayTypeInfo = &theShortArrayTypeInfoImpl; +extern const TypeInfo* theStringTypeInfo = &theStringTypeInfoImpl; +extern const TypeInfo* theThrowableTypeInfo = &theThrowableTypeInfoImpl; +extern const TypeInfo* theUnitTypeInfo = &theUnitTypeInfoImpl; +extern const TypeInfo* theWorkerBoundReferenceTypeInfo = &theWorkerBoundReferenceTypeInfoImpl; + +extern const ArrayHeader theEmptyArray = { &theArrayTypeInfoImpl, /* element count */0 }; + +OBJ_GETTER0(TheEmptyString) { + RETURN_OBJ(theEmptyStringImpl.obj()); +} + +RUNTIME_NORETURN OBJ_GETTER(makeWeakReferenceCounter, void*) { + THROW_NOT_IMPLEMENTED +} + +RUNTIME_NORETURN OBJ_GETTER(makePermanentWeakReferenceImpl, void*) { + THROW_NOT_IMPLEMENTED +} + +RUNTIME_NORETURN OBJ_GETTER(makeObjCWeakReferenceImpl, void*) { + THROW_NOT_IMPLEMENTED +} + +void checkRangeIndexes(KInt from, KInt to, KInt size) { + if (from < 0 || to > size) { + throw std::out_of_range("Index out of bounds: from=" + std::to_string(from) + + ", to=" + std::to_string(to) + + ", size=" + std::to_string(size)); + } + if (from > to) { + throw std::invalid_argument("Illegal argument: from > to, from=" + std::to_string(from) + ", to=" + std::to_string(to)); + } +} + +RUNTIME_NORETURN OBJ_GETTER(WorkerLaunchpad, KRef) { + THROW_NOT_IMPLEMENTED +} + +void RUNTIME_NORETURN ThrowWorkerInvalidState() { + THROW_NOT_IMPLEMENTED +} + +void RUNTIME_NORETURN ThrowNullPointerException() { + THROW_NOT_IMPLEMENTED +} + +void RUNTIME_NORETURN ThrowArrayIndexOutOfBoundsException() { + THROW_NOT_IMPLEMENTED +} + +void RUNTIME_NORETURN ThrowClassCastException(const ObjHeader* instance, const TypeInfo* type_info) { + THROW_NOT_IMPLEMENTED +} + +void RUNTIME_NORETURN ThrowArithmeticException() { + THROW_NOT_IMPLEMENTED +} + +void RUNTIME_NORETURN ThrowNumberFormatException() { + THROW_NOT_IMPLEMENTED +} + +void RUNTIME_NORETURN ThrowOutOfMemoryError() { + THROW_NOT_IMPLEMENTED +} + +void RUNTIME_NORETURN ThrowNotImplementedError() { + THROW_NOT_IMPLEMENTED +} + +void RUNTIME_NORETURN ThrowCharacterCodingException() { + THROW_NOT_IMPLEMENTED +} + +void RUNTIME_NORETURN ThrowIllegalArgumentException() { + THROW_NOT_IMPLEMENTED +} + +void RUNTIME_NORETURN ThrowIllegalStateException() { + THROW_NOT_IMPLEMENTED +} + +void RUNTIME_NORETURN ThrowInvalidMutabilityException(KConstRef where) { + THROW_NOT_IMPLEMENTED +} + +void RUNTIME_NORETURN ThrowIncorrectDereferenceException() { + THROW_NOT_IMPLEMENTED +} + +void RUNTIME_NORETURN ThrowIllegalObjectSharingException(KConstNativePtr typeInfo, KConstNativePtr address) { + THROW_NOT_IMPLEMENTED +} + +void RUNTIME_NORETURN ThrowFreezingException(KRef toFreeze, KRef blocker) { + THROW_NOT_IMPLEMENTED +} + +void ReportUnhandledException(KRef throwable) { + konan::consolePrintf("Uncaught Kotlin exception."); +} + +RUNTIME_NORETURN OBJ_GETTER(DescribeObjectForDebugging, KConstNativePtr typeInfo, KConstNativePtr address) { + THROW_NOT_IMPLEMENTED +} + +void ExceptionReporterLaunchpad(KRef reporter, KRef throwable) { + THROW_NOT_IMPLEMENTED +} + +void Kotlin_WorkerBoundReference_freezeHook(KRef thiz) { + THROW_NOT_IMPLEMENTED +} + +extern const KBoolean BOOLEAN_RANGE_FROM = false; +extern const KBoolean BOOLEAN_RANGE_TO = true; +extern KBox BOOLEAN_CACHE[] = { + {{}, false}, + {{}, true}, +}; + +OBJ_GETTER(Kotlin_boxBoolean, KBoolean value) { + if (value) { + RETURN_OBJ(&BOOLEAN_CACHE[1].header); + } else { + RETURN_OBJ(&BOOLEAN_CACHE[0].header); + } +} + +extern const KByte BYTE_RANGE_FROM = -1; +extern const KByte BYTE_RANGE_TO = 1; +extern KBox BYTE_CACHE[] = { + {{}, -1}, + {{}, 0}, + {{}, 1}, +}; + +extern const KChar CHAR_RANGE_FROM = 0; +extern const KChar CHAR_RANGE_TO = 2; +extern KBox CHAR_CACHE[] = { + {{}, 0}, + {{}, 1}, + {{}, 2}, +}; + +extern const KShort SHORT_RANGE_FROM = -1; +extern const KShort SHORT_RANGE_TO = 1; +extern KBox SHORT_CACHE[] = { + {{}, -1}, + {{}, 0}, + {{}, 1}, +}; + +extern const KInt INT_RANGE_FROM = -1; +extern const KInt INT_RANGE_TO = 1; +extern KBox INT_CACHE[] = { + {{}, -1}, + {{}, 0}, + {{}, 1}, +}; + +extern const KLong LONG_RANGE_FROM = -1; +extern const KLong LONG_RANGE_TO = 1; +extern KBox LONG_CACHE[] = { + {{}, -1}, + {{}, 0}, + {{}, 1}, +}; + +RUNTIME_NORETURN OBJ_GETTER(Kotlin_Throwable_getMessage, KRef throwable) { + THROW_NOT_IMPLEMENTED +} + +} // extern "C" diff --git a/runtime/src/test_support/cpp/CompilerGenerated.h b/runtime/src/test_support/cpp/CompilerGenerated.h new file mode 100644 index 00000000000..47b6730b9b9 --- /dev/null +++ b/runtime/src/test_support/cpp/CompilerGenerated.h @@ -0,0 +1,11 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +#ifndef RUNTIME_COMPILER_GENERATED_H +#define RUNTIME_COMPILER_GENERATED_H + +#define THROW_NOT_IMPLEMENTED throw std::runtime_error("Not implemented for tests"); + +#endif //RUNTIME_COMPILER_GENERATED_H diff --git a/runtime/src/test_support/cpp/CompilerGeneratedObjC.mm b/runtime/src/test_support/cpp/CompilerGeneratedObjC.mm new file mode 100644 index 00000000000..23e7e105047 --- /dev/null +++ b/runtime/src/test_support/cpp/CompilerGeneratedObjC.mm @@ -0,0 +1,71 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +#if KONAN_OBJC_INTEROP + +#include +#include + +#include "CompilerGenerated.h" +#include "Types.h" + +extern "C" { + +Class Kotlin_Interop_getObjCClass(const char* name) { + Class result = objc_lookUpClass(name); + if (result == nil) { + // GTest can display error messages of C++ exceptions so we use them instead of ObjC ones. + throw std::invalid_argument("Incorrect class name"); + } + return result; +} + +RUNTIME_NORETURN OBJ_GETTER0(Kotlin_NSEnumeratorAsKIterator_create) { + THROW_NOT_IMPLEMENTED +} + +void Kotlin_NSEnumeratorAsKIterator_done(KRef thiz) { + THROW_NOT_IMPLEMENTED +} + +void Kotlin_NSEnumeratorAsKIterator_setNext(KRef thiz, KRef value) { + THROW_NOT_IMPLEMENTED +} + +RUNTIME_NORETURN OBJ_GETTER(Kotlin_ObjCExport_NSErrorAsExceptionImpl, KRef message, KRef error) { + THROW_NOT_IMPLEMENTED +} + +void Kotlin_ObjCExport_ThrowCollectionConcurrentModification() { + THROW_NOT_IMPLEMENTED +} + +void Kotlin_ObjCExport_ThrowCollectionTooLarge() { + THROW_NOT_IMPLEMENTED +} + +typedef OBJ_GETTER((*convertReferenceFromObjC), id obj); +extern convertReferenceFromObjC Kotlin_ObjCExport_blockToFunctionConverters[] = {}; +extern int Kotlin_ObjCExport_blockToFunctionConverters_size = 0; + +RUNTIME_NORETURN OBJ_GETTER(Kotlin_ObjCExport_createContinuationArgumentImpl, KRef completionHolder, const TypeInfo** exceptionTypes) { + THROW_NOT_IMPLEMENTED +} + +RUNTIME_NORETURN OBJ_GETTER(Kotlin_ObjCExport_getWrappedError, KRef throwable) { + THROW_NOT_IMPLEMENTED +} + +void Kotlin_ObjCExport_resumeContinuationFailure(KRef continuation, KRef exception) { + THROW_NOT_IMPLEMENTED +} + +void Kotlin_ObjCExport_resumeContinuationSuccess(KRef continuation, KRef result) { + THROW_NOT_IMPLEMENTED +} + +} // extern "C" + +#endif // KONAN_OBJC_INTEROP \ No newline at end of file diff --git a/runtime/src/test_support/cpp/TestLauncher.cpp b/runtime/src/test_support/cpp/TestLauncher.cpp new file mode 100644 index 00000000000..0c411dfdafc --- /dev/null +++ b/runtime/src/test_support/cpp/TestLauncher.cpp @@ -0,0 +1,12 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +int main(int argc, char **argv) { + testing::InitGoogleMock(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/exec/ExecuteCommand.kt b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/exec/ExecuteCommand.kt index 27e93f9d86c..47ae615b5b7 100644 --- a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/exec/ExecuteCommand.kt +++ b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/exec/ExecuteCommand.kt @@ -28,6 +28,8 @@ open class Command(initialCommand: List) { constructor(vararg command: String) : this(command.toList()) protected val command = initialCommand.toMutableList() + val argsWithExecutable: List = command + val args: List get() = command.drop(1)