[K/N] FileCheck-based tests
Introduce a simple infrastructure for testing produced bitcode with FileCheck LLVM utility. ^KT-48925
This commit is contained in:
@@ -144,6 +144,8 @@ allprojects {
|
|||||||
|
|
||||||
// backent.native/tests/coverage
|
// backent.native/tests/coverage
|
||||||
ext.testOutputCoverage = rootProject.file("$testOutputRoot/coverage")
|
ext.testOutputCoverage = rootProject.file("$testOutputRoot/coverage")
|
||||||
|
|
||||||
|
ext.testOutputFileCheck = rootProject.file("$testOutputRoot/filecheck")
|
||||||
}
|
}
|
||||||
testOutputExternal.mkdirs()
|
testOutputExternal.mkdirs()
|
||||||
testOutputStdlib.mkdirs()
|
testOutputStdlib.mkdirs()
|
||||||
@@ -211,6 +213,7 @@ task sanity {
|
|||||||
// Add regular gradle test tasks
|
// Add regular gradle test tasks
|
||||||
dependsOn(tasksOf(Test))
|
dependsOn(tasksOf(Test))
|
||||||
dependsOn(tasksOf(CoverageTest))
|
dependsOn(tasksOf(CoverageTest))
|
||||||
|
dependsOn(tasksOf(FileCheckTest))
|
||||||
dependsOn(":kotlin-native:Interop:Indexer:check")
|
dependsOn(":kotlin-native:Interop:Indexer:check")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5811,6 +5814,53 @@ pluginTest("runtime_basic_init", "nopPlugin") {
|
|||||||
flags = ["-tr"]
|
flags = ["-tr"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Task fileCheckTest(String name, Closure<FileCheckTest> configureClosure) {
|
||||||
|
return project.tasks.create(name, FileCheckTest) { task ->
|
||||||
|
task.configure(configureClosure)
|
||||||
|
task.llvmIr = project.file("$testOutputFileCheck/$name/out.ll")
|
||||||
|
if (task.enabled) {
|
||||||
|
konanArtifacts {
|
||||||
|
// We could use `bitcode` here to make things faster. But:
|
||||||
|
// 1. `bitcode` has no other usages.
|
||||||
|
// 2. Unification should help porting to the new test infra.
|
||||||
|
// 3. Compiler might behave differently when outputting bitcode instead of object files.
|
||||||
|
program(name, targets: [ target ]) {
|
||||||
|
srcFiles task.annotatedSource
|
||||||
|
baseDir "$testOutputFileCheck/$name"
|
||||||
|
extraOpts project.globalTestArgs
|
||||||
|
extraOpts "-Xtemporary-files-dir=$testOutputFileCheck/$name", "-Xsave-llvm-ir"
|
||||||
|
}
|
||||||
|
UtilsKt.dependsOnKonanBuildingTask(task, name, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileCheckTest("filecheck_smoke0") {
|
||||||
|
annotatedSource = project.file('filecheck/smoke0.kt')
|
||||||
|
}
|
||||||
|
|
||||||
|
fileCheckTest("filecheck_replace_invoke_with_call") {
|
||||||
|
annotatedSource = project.file('filecheck/replace_invoke_with_call.kt')
|
||||||
|
}
|
||||||
|
|
||||||
|
fileCheckTest("filecheck_intrinsics") {
|
||||||
|
annotatedSource = project.file('filecheck/intrinsics.kt')
|
||||||
|
}
|
||||||
|
|
||||||
|
fileCheckTest("filecheck_escape_analysis_enabled") {
|
||||||
|
annotatedSource = project.file('filecheck/escape_analysis.kt')
|
||||||
|
enabled = project.globalTestArgs.contains('-opt')
|
||||||
|
checkPrefix = "CHECK-OPT"
|
||||||
|
}
|
||||||
|
|
||||||
|
fileCheckTest("filecheck_escape_analysis_disabled") {
|
||||||
|
annotatedSource = project.file('filecheck/escape_analysis.kt')
|
||||||
|
enabled = project.globalTestArgs.contains('-g')
|
||||||
|
checkPrefix = "CHECK-DEBUG"
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
nopPluginApi project(kotlinCompilerModule)
|
nopPluginApi project(kotlinCompilerModule)
|
||||||
nopPluginApi project(":native:kotlin-native-utils")
|
nopPluginApi project(":native:kotlin-native-utils")
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class A(val x: Int)
|
||||||
|
|
||||||
|
// CHECK-LABEL: "kfun:#main(){}"
|
||||||
|
fun main() {
|
||||||
|
// CHECK-DEBUG: call %struct.ObjHeader* @AllocInstance
|
||||||
|
// CHECK-OPT: alloca %"kclassbody:A#internal"
|
||||||
|
val a = A(5)
|
||||||
|
println(a.x)
|
||||||
|
// CHECK-LABEL: epilogue
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// CHECK-LABEL: "kfun:#and(kotlin.Int;kotlin.Int){}kotlin.Int"
|
||||||
|
fun and(a: Int, b: Int): Int {
|
||||||
|
// CHECK: and {{.*}}, {{.*}}
|
||||||
|
return a and b
|
||||||
|
// CHECK-LABEL: epilogue
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: "kfun:#ieee754(kotlin.Float;kotlin.Float){}kotlin.Boolean"
|
||||||
|
fun ieee754(a: Float, b: Float): Boolean {
|
||||||
|
// CHECK: fcmp oeq float {{.*}}, {{.*}}
|
||||||
|
return a == b
|
||||||
|
// CHECK-LABEL: epilogue
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: "kfun:#main(){}"
|
||||||
|
fun main() {
|
||||||
|
val x = and(1, 2)
|
||||||
|
val y = ieee754(0.0f, 1.0f)
|
||||||
|
// CHECK-LABEL: epilogue
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun flameThrower() {
|
||||||
|
throw Throwable("🔥")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: "kfun:#f1(){}"
|
||||||
|
fun f1() {
|
||||||
|
// CHECK: call void @"kfun:#flameThrower(){}"()
|
||||||
|
flameThrower()
|
||||||
|
// CHECK-LABEL: epilogue
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: "kfun:#f2(){}"
|
||||||
|
fun f2() {
|
||||||
|
try {
|
||||||
|
// CHECK: invoke void @"kfun:#flameThrower(){}"()
|
||||||
|
flameThrower()
|
||||||
|
} catch (t: Throwable) {}
|
||||||
|
// CHECK-LABEL: epilogue
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: "kfun:#main(){}"
|
||||||
|
fun main() {
|
||||||
|
f1()
|
||||||
|
f2()
|
||||||
|
// CHECK-LABEL: epilogue
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// CHECK-LABEL: "kfun:#id(kotlin.Any?){}kotlin.Any?"
|
||||||
|
fun id(a: Any?): Any? {
|
||||||
|
return a
|
||||||
|
// CHECK-LABEL: epilogue
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: "kfun:#main(){}"
|
||||||
|
fun main() {
|
||||||
|
// CHECK: invoke %struct.ObjHeader* @"kfun:#id(kotlin.Any?){}kotlin.Any?"
|
||||||
|
val x = id("Hello")
|
||||||
|
// CHECK: invoke void @"kfun:kotlin.io#println(kotlin.Any?){}"(%struct.ObjHeader* {{.*}})
|
||||||
|
println(x)
|
||||||
|
// CHECK-LABEL: epilogue
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 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
|
||||||
|
|
||||||
|
import groovy.lang.Closure
|
||||||
|
import org.gradle.api.DefaultTask
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.Task
|
||||||
|
import org.gradle.api.tasks.*
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.tasks.KonanCompileProgramTask
|
||||||
|
import org.jetbrains.kotlin.konan.target.AppleConfigurables
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.tasks.KonanCompileTask
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Files
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gradle task that wraps FileCheck LLVM utility.
|
||||||
|
*/
|
||||||
|
open class FileCheckTest : DefaultTask() {
|
||||||
|
|
||||||
|
private val target = project.testTarget
|
||||||
|
private val platform = project.platformManager.platform(target)
|
||||||
|
private val configurables = platform.configurables
|
||||||
|
|
||||||
|
private val llvmBin = "${configurables.absoluteLlvmHome}/bin"
|
||||||
|
|
||||||
|
private val fileCheck = "$llvmBin/FileCheck"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File annotated with FileCheck directives.
|
||||||
|
*/
|
||||||
|
@InputFile
|
||||||
|
lateinit var annotatedSource: File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LLVM IR that should match [annotatedSource].
|
||||||
|
*/
|
||||||
|
@get:Internal
|
||||||
|
lateinit var llvmIr: File
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
fun run() {
|
||||||
|
runFileCheck(annotatedSource.toPath(), llvmIr.toPath())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What prefix should checked for pattern instead of default CHECK?
|
||||||
|
*/
|
||||||
|
@Input
|
||||||
|
@Optional
|
||||||
|
var checkPrefix: String? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that [inputFile] matches [annotatedFile] with FileCheck.
|
||||||
|
*/
|
||||||
|
private fun runFileCheck(annotatedFile: Path, inputFile: Path): ProcessOutput {
|
||||||
|
val args = mutableListOf(annotatedFile.toAbsolutePath().toString(), "--input-file", inputFile.toAbsolutePath().toString())
|
||||||
|
checkPrefix?.let {
|
||||||
|
args.addAll(listOf("--check-prefix", it))
|
||||||
|
}
|
||||||
|
return runProcess(localExecutor(project), fileCheck, *args.toTypedArray())
|
||||||
|
.ensureSuccessful(fileCheck, *args.toTypedArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ProcessOutput.ensureSuccessful(vararg command: String): ProcessOutput {
|
||||||
|
if (exitCode != 0) {
|
||||||
|
println("""
|
||||||
|
${command.joinToString(separator = " ")} failed.
|
||||||
|
exitCode: $exitCode
|
||||||
|
stdout:
|
||||||
|
$stdOut
|
||||||
|
stderr:
|
||||||
|
$stdErr
|
||||||
|
""".trimIndent())
|
||||||
|
throw TestFailedException("${command.joinToString(separator = " ")} failed")
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user