rrn/rd/KT-55578 User-provided hints for linker errors

[K/N] KT-55578 Show customized user suggestions on linkage errors

Merge-request: KT-MR-9636
Merged-by: Gleb Lukianets <Gleb.Lukianets@jetbrains.com>
This commit is contained in:
Gleb Lukianets
2023-04-27 14:14:43 +00:00
committed by Space Team
parent 1e0115aef8
commit 242ca73d83
18 changed files with 368 additions and 11 deletions
@@ -0,0 +1,10 @@
headerFilter = **/userSetupHint.h
userSetupHint = 🤌\t\u2115ever put ketchup on-a 🍝\
\n = `\u2115` = "\u2115" = \\u2115\
\n🇮🇹
---
void foo();
void test() {
foo();
}
@@ -0,0 +1,10 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
import userSetupFancyHint.*
fun userSetupHint(args: Array<String>) {
test()
}
@@ -0,0 +1,7 @@
#include <stdio.h>
#ifdef __cplusplus
#define EXPORT extern "C"
#else
#define EXPORT
#endif
@@ -0,0 +1,12 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
import userSetupHint1.*
import userSetupHint2.*
fun userSetupHint(args: Array<String>) {
testFoo()
testBar()
}
@@ -0,0 +1,5 @@
#import "userSetupHint.h"
EXPORT void foo() {
// this just needs to exist
}
@@ -0,0 +1,8 @@
headerFilter = **/userSetupHint.h
userSetupHint = <<HINT1>>
---
void foo(void);
void testFoo() {
foo();
}
@@ -0,0 +1,5 @@
#import "userSetupHint.h"
EXPORT void bar() {
// this just needs to exist
}
@@ -0,0 +1,8 @@
headerFilter = **/userSetupHint.h
userSetupHint = <<HINT2>>
---
void bar(void);
void testBar() {
bar();
}
@@ -0,0 +1,5 @@
headerFilter = **/userSetupHint.h
userSetupHint = <<HINT_MISSING_LIBRARY>>
linkerOpts = -lNonExistant
---
void test() { }
@@ -0,0 +1,5 @@
import userSetupHintLinkingMissingLibrary.*
fun userSetupHint(args: Array<String>) {
test()
}
@@ -0,0 +1,7 @@
headerFilter = **/userSetupHint.h
---
void foo(void);
void test() {
foo();
}
@@ -0,0 +1,10 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
import userSetupNoHint.*
fun userSetupHint(args: Array<String>) {
test()
}
@@ -0,0 +1,54 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.konan.blackboxtest
import org.jetbrains.kotlin.konan.blackboxtest.support.*
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.*
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRunChecks
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.KotlinNativeTargets
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Timeouts
@EnforcedProperty(ClassLevelProperty.COMPILER_OUTPUT_INTERCEPTOR, "NONE")
abstract class AbstractNativeLinkerOutputTest : AbstractNativeCInteropBaseTest() {
private fun createTestCaseNoTestRun(module: TestModule.Exclusive, compilerArgs: TestCompilerArgs) = TestCase(
id = TestCaseId.Named(module.name),
kind = TestKind.STANDALONE_NO_TR,
modules = setOf(module),
freeCompilerArgs = compilerArgs,
nominalPackageName = PackageName.EMPTY,
checks = TestRunChecks.Default(testRunSettings.get<Timeouts>().executionTimeout),
extras = TestCase.NoTestRunnerExtras(".${module.name}")
).apply {
initialize(null, null)
}
internal fun compileToExecutable(
module: TestModule.Exclusive,
dependencies: List<TestCompilationDependency<*>>,
args: List<String> = emptyList()
) = compileToExecutable(
createTestCaseNoTestRun(module, TestCompilerArgs(args)),
dependencies
)
internal fun compileToExecutable(
testCase: TestCase,
dependencies: List<TestCompilationDependency<*>>
): TestCompilationResult<out TestCompilationArtifact.Executable> {
val compilation = ExecutableCompilation(
settings = testRunSettings,
freeCompilerArgs = testCase.freeCompilerArgs,
sourceModules = testCase.modules,
extras = TestCase.NoTestRunnerExtras(".${testCase.modules.singleOrNull()!!.name}"),
dependencies = dependencies,
expectedArtifact = getExecutableArtifact()
)
return compilation.result
}
private fun getExecutableArtifact() =
TestCompilationArtifact.Executable(buildDir.resolve("app." + testRunSettings.get<KotlinNativeTargets>().testTarget.family.exeSuffix))
}
@@ -0,0 +1,177 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.konan.blackboxtest
import com.intellij.testFramework.TestDataPath
import org.jetbrains.kotlin.konan.blackboxtest.support.*
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationArtifact
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationArtifact.KLIB
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult.Companion.assertSuccess
import org.junit.jupiter.api.Test
import java.io.File
import kotlin.test.assertContains
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@TestDataPath("\$PROJECT_ROOT")
@EnforcedProperty(ClassLevelProperty.COMPILER_OUTPUT_INTERCEPTOR, "NONE")
class LinkerOutputTestKT55578 : AbstractNativeLinkerOutputTest() {
private val testDir = File("native/native.tests/testData/CInterop/KT-55578/")
private val hint1 = "<<HINT1>>"
private val hint2 = "<<HINT2>>"
private val cliHint = "<<CLI>>"
private val hintMissingLibrary = "<<HINT_MISSING_LIBRARY>>"
private val hintFancy = "\uD83E\uDD0C\tever put ketchup on-a \uD83C\uDF5D\n = `` = \"\\u2115\" = \\u2115\n\uD83C\uDDEE\uD83C\uDDF9\n"
@Test
fun `should print hints on failed linkage`() {
val targetLibrary1 = compileKlib(testDir.resolve("userSetupHint1.def"))
val targetLibrary2 = compileKlib(testDir.resolve("userSetupHint2.def"))
val compilationResult = compileExecutable(testDir.resolve("userSetupHint.kt"), targetLibrary1, targetLibrary2)
assertTrue(compilationResult is TestCompilationResult.Failure, "Compilation is expected to fail with linkage errors")
val compilationOutput = compilationResult.loggedData.toString()
for (hint in arrayOf(hint1, hint2)) {
assertContains(compilationOutput, hint, false, """
|Error output should contain provided hint "$hint"
|Actual output:
|$compilationOutput
""".trimMargin())
}
}
@Test
fun `should print fancy hints`() {
val targetLibrary = compileKlib(testDir.resolve("userSetupFancyHint.def"))
val compilationResult = compileExecutable(testDir.resolve("userSetupFancyHint.kt"), targetLibrary)
assertTrue(compilationResult is TestCompilationResult.Failure, "Compilation is expected to fail with linkage errors")
val compilationOutput = compilationResult.loggedData.toString()
assertContains(compilationOutput, hintFancy, false, """
|Error output should contain provided hint
|Actual output:
|$compilationOutput
""".trimMargin())
}
@Test
fun `should print hints on missing library`() {
val targetLibrary = compileKlib(testDir.resolve("userSetupHintLinkingMissingLibrary.def"))
val compilationResult = compileExecutable(testDir.resolve("userSetupHintLinkingMissingLibrary.kt"), targetLibrary)
assertTrue(compilationResult is TestCompilationResult.Failure, "Compilation is expected to fail with linkage errors")
val compilationOutput = compilationResult.loggedData.toString()
for (hint in arrayOf(hintMissingLibrary)) {
assertContains(compilationOutput, hint, false, """
|Error output should contain provided hint "$hint"
|Actual output:
|$compilationOutput
""".trimMargin())
}
}
@Test
fun `should not print hints on successful compilation`() {
val targetLibrary1 = compileKlib(
testDir.resolve("userSetupHint1.def"),
testDir.resolve("userSetupHint1.c")
)
val targetLibrary2 = compileKlib(
testDir.resolve("userSetupHint2.def"),
testDir.resolve("userSetupHint2.c")
)
val compilationResult = compileExecutable(
testDir.resolve("userSetupHint.kt"),
targetLibrary1, targetLibrary2
)
val compilationOutput = compilationResult.assertSuccess().loggedData.toString()
for (hint in arrayOf(hint1, hint2)) {
assertFalse(compilationOutput.contains(hint), """
|Error output should *not* contain provided hint "$hint"
|Actual output:
|$compilationOutput
""".trimMargin())
}
}
@Test
fun `should not print hints on compilation failed without linker errors`() {
val targetLibrary1 = compileKlib(
testDir.resolve("userSetupHint1.def"),
testDir.resolve("userSetupHint1.c")
)
val compilationResult = compileExecutable(testDir.resolve("userSetupHint.kt"), targetLibrary1)
assertTrue(compilationResult is TestCompilationResult.Failure, "Compilation is expected to fail")
val compilationOutput = compilationResult.loggedData.toString()
for (hint in arrayOf(hint1, hint2)) {
assertFalse(compilationOutput.contains(hint), """
|Error output should *not* contain provided hint "$hint"
|Actual output:
|$compilationOutput
""".trimMargin())
}
}
@Test
fun `should shadow hint by cli argument`() {
val targetLibrary1 = compileKlib(testDir.resolve("userSetupHint1.def"), extraArgs = listOf("-Xuser-setup-hint", cliHint))
val targetLibrary2 = compileKlib(testDir.resolve("userSetupHint2.def"))
val compilationResult = compileExecutable(testDir.resolve("userSetupHint.kt"), targetLibrary1, targetLibrary2)
assertTrue(compilationResult is TestCompilationResult.Failure, "Compilation is expected to fail")
val compilationOutput = compilationResult.loggedData.toString()
for (hint in arrayOf(cliHint, hint2)) {
assertContains(compilationOutput, hint, false, """
|Error output should contain provided hint "$hint"
|Actual output:
|$compilationOutput
""".trimMargin())
}
}
@Test
fun `should not print hints on compilation failed with no provided hints`() {
val targetLibrary = compileKlib(testDir.resolve("userSetupNoHint.def"))
val compilationResult = compileExecutable(testDir.resolve("userSetupNoHint.kt"), targetLibrary)
assertTrue(compilationResult is TestCompilationResult.Failure, "Compilation is expected to fail")
val compilationOutput = compilationResult.loggedData.toString()
assertFalse(compilationOutput.contains("It seems your project produced link errors."), """
|Error output should *not* contain any hints
|Actual output:
|$compilationOutput
""".trimMargin())
}
private fun compileExecutable(
file: File,
vararg libraries: KLIB,
extraArgs: List<String> = emptyList()
): TestCompilationResult<out TestCompilationArtifact.Executable> {
val module = TestModule.Exclusive("userSetupHint", emptySet(), emptySet(), emptySet()).apply {
files += TestFile.createCommitted(file, this)
}
return compileToExecutable(module, libraries.asList().map { it.asLibraryDependency() }, extraArgs)
}
private fun compileKlib(defFile: File, sourceFile: File? = null, extraArgs: List<String> = emptyList()): KLIB {
val sourceArguments = sourceFile?.let { listOf("-Xcompile-source", sourceFile.absolutePath) } ?: emptyList()
val libraryTestCase: TestCase = generateCInteropTestCaseWithSingleDef(defFile, extraArgs + sourceArguments)
return libraryTestCase.cinteropToLibrary().assertSuccess().resultingArtifact
}
}