[K/N] Remove LLVM coverage
The -Xcoverage feature has not worked and has been disabled for a while. This fix removes it, and all of its uses. Co-authored-by: Troels Lund <troels@google.com> Merge-request: KOTLIN-MR-821 Merged-by: Alexander Shabalin <alexander.shabalin@jetbrains.com>
This commit is contained in:
committed by
Space Cloud
parent
e0c931f69d
commit
4f77434ea5
-12
@@ -57,18 +57,6 @@ internal fun getIncludedLibraries(
|
||||
allowDefaultLibs = false
|
||||
)
|
||||
|
||||
internal fun getCoveredLibraries(
|
||||
configuration: CompilerConfiguration,
|
||||
resolvedLibraries: KotlinLibraryResolveResult,
|
||||
resolver: SearchPathResolver<KonanLibrary>
|
||||
): List<KonanLibrary> = getFeaturedLibraries(
|
||||
configuration.getList(KonanConfigKeys.LIBRARIES_TO_COVER),
|
||||
resolvedLibraries,
|
||||
resolver,
|
||||
FeaturedLibrariesReporter.forCoveredLibraries(configuration),
|
||||
allowDefaultLibs = true
|
||||
)
|
||||
|
||||
private sealed class FeaturedLibrariesReporter {
|
||||
|
||||
abstract fun reportIllegalKind(library: KonanLibrary)
|
||||
|
||||
-4
@@ -308,9 +308,6 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
|
||||
return resolvedLibraries.filterRoots { (!it.isDefault && !this.purgeUserLibs) || it.isNeededForLink }.getFullList(TopologicalLibraryOrder).map { it as KonanLibrary }
|
||||
}
|
||||
|
||||
val shouldCoverSources = configuration.getBoolean(KonanConfigKeys.COVERAGE)
|
||||
private val shouldCoverLibraries = !configuration.getList(KonanConfigKeys.LIBRARIES_TO_COVER).isNullOrEmpty()
|
||||
|
||||
private val defaultAllocationMode get() =
|
||||
if (sanitizer == null)
|
||||
AllocationMode.CUSTOM
|
||||
@@ -377,7 +374,6 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
|
||||
GC.CONCURRENT_MARK_AND_SWEEP -> add("concurrent_ms_gc.bc")
|
||||
}
|
||||
}
|
||||
if (shouldCoverLibraries || shouldCoverSources) add("profileRuntime.bc")
|
||||
if (target.supportsCoreSymbolication()) {
|
||||
add("source_info_core_symbolication.bc")
|
||||
}
|
||||
|
||||
-6
@@ -135,12 +135,6 @@ class KonanConfigKeys {
|
||||
= CompilerConfigurationKey.create("verify compiler")
|
||||
val DEBUG_INFO_VERSION: CompilerConfigurationKey<Int>
|
||||
= CompilerConfigurationKey.create("debug info format version")
|
||||
val COVERAGE: CompilerConfigurationKey<Boolean>
|
||||
= CompilerConfigurationKey.create("emit coverage info for sources")
|
||||
val LIBRARIES_TO_COVER: CompilerConfigurationKey<List<String>>
|
||||
= CompilerConfigurationKey.create("libraries that should be covered")
|
||||
val PROFRAW_PATH: CompilerConfigurationKey<String?>
|
||||
= CompilerConfigurationKey.create("path to *.profraw coverage output")
|
||||
val OBJC_GENERICS: CompilerConfigurationKey<Boolean>
|
||||
= CompilerConfigurationKey.create("write objc header with generics support")
|
||||
val DEBUG_PREFIX_MAP: CompilerConfigurationKey<Map<String, String>>
|
||||
|
||||
-3
@@ -71,9 +71,6 @@ class KonanLibrariesResolveSupport(
|
||||
internal val exportedLibraries =
|
||||
getExportedLibraries(configuration, resolvedLibraries, resolver.searchPathResolver, report = true)
|
||||
|
||||
internal val coveredLibraries =
|
||||
getCoveredLibraries(configuration, resolvedLibraries, resolver.searchPathResolver)
|
||||
|
||||
internal val includedLibraries =
|
||||
getIncludedLibraries(includedLibraryFiles, configuration, resolvedLibraries)
|
||||
}
|
||||
|
||||
+1
-3
@@ -40,7 +40,6 @@ internal fun determineLinkerOutput(context: PhaseContext): LinkerOutputKind =
|
||||
internal class Linker(
|
||||
private val config: KonanConfig,
|
||||
private val linkerOutput: LinkerOutputKind,
|
||||
private val isCoverageEnabled: Boolean = false,
|
||||
private val outputFiles: OutputFiles,
|
||||
) {
|
||||
private val platform = config.platform
|
||||
@@ -147,7 +146,6 @@ internal class Linker(
|
||||
debug = debug,
|
||||
kind = linkerOutput,
|
||||
outputDsymBundle = outputFiles.symbolicInfoFile,
|
||||
needsProfileLibrary = isCoverageEnabled,
|
||||
mimallocEnabled = config.allocationMode == AllocationMode.MIMALLOC,
|
||||
sanitizer = config.sanitizer
|
||||
)
|
||||
@@ -189,4 +187,4 @@ internal fun runLinkerCommands(context: PhaseContext, commands: List<Command>, c
|
||||
val extraInfo = listOfNotNull(extraUserInfo, extraUserSetupInfo).joinToString(separator = "\n")
|
||||
|
||||
context.reportCompilationError("${e.toolName} invocation reported errors\n$extraInfo\n${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
+1
-4
@@ -13,7 +13,6 @@ import org.jetbrains.kotlin.backend.konan.driver.PhaseContext
|
||||
import org.jetbrains.kotlin.backend.konan.driver.utilities.BackendContextHolder
|
||||
import org.jetbrains.kotlin.backend.konan.driver.utilities.LlvmIrHolder
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.*
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.coverage.CoverageManager
|
||||
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExport
|
||||
import org.jetbrains.kotlin.backend.konan.serialization.SerializedClassFields
|
||||
import org.jetbrains.kotlin.backend.konan.serialization.SerializedEagerInitializedFile
|
||||
@@ -101,8 +100,6 @@ internal class NativeGenerationState(
|
||||
|
||||
val virtualFunctionTrampolines = mutableMapOf<IrSimpleFunction, LlvmCallable>()
|
||||
|
||||
val coverage by lazy { CoverageManager(this) }
|
||||
|
||||
lateinit var objCExport: ObjCExport
|
||||
|
||||
fun hasDebugInfo() = debugInfoDelegate.isInitialized()
|
||||
@@ -144,4 +141,4 @@ internal class NativeGenerationState(
|
||||
|
||||
override val llvmModule: LLVMModuleRef
|
||||
get() = llvm.module
|
||||
}
|
||||
}
|
||||
|
||||
-3
@@ -172,9 +172,6 @@ fun CompilerConfiguration.setupFromArguments(arguments: K2NativeCompilerArgument
|
||||
|
||||
put(BITCODE_EMBEDDING_MODE, selectBitcodeEmbeddingMode(this@setupFromArguments, arguments))
|
||||
put(DEBUG_INFO_VERSION, arguments.debugInfoFormatVersion.toInt())
|
||||
put(COVERAGE, arguments.coverage)
|
||||
put(LIBRARIES_TO_COVER, arguments.coveredLibraries.toNonNullList())
|
||||
arguments.coverageFile?.let { put(PROFRAW_PATH, it) }
|
||||
put(OBJC_GENERICS, !arguments.noObjcGenerics)
|
||||
put(DEBUG_PREFIX_MAP, parseDebugPrefixMap(arguments, this@setupFromArguments))
|
||||
|
||||
|
||||
-11
@@ -20,7 +20,6 @@ import org.jetbrains.kotlin.backend.konan.driver.PhaseEngine
|
||||
import org.jetbrains.kotlin.backend.konan.driver.utilities.LlvmIrHolder
|
||||
import org.jetbrains.kotlin.backend.konan.driver.utilities.getDefaultLlvmModuleActions
|
||||
import org.jetbrains.kotlin.backend.konan.insertAliasToEntryPoint
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.coverage.runCoveragePass
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.verifyModule
|
||||
import org.jetbrains.kotlin.backend.konan.optimizations.RemoveRedundantSafepointsPass
|
||||
import org.jetbrains.kotlin.backend.konan.optimizations.removeMultipleThreadDataLoads
|
||||
@@ -102,13 +101,6 @@ internal val ThreadSanitizerPhase = optimizationPipelinePass(
|
||||
pipeline = ::ThreadSanitizerPipeline,
|
||||
)
|
||||
|
||||
internal val CoveragePhase = createSimpleNamedCompilerPhase<NativeGenerationState, Unit>(
|
||||
name = "Coverage",
|
||||
description = "Produce coverage information",
|
||||
postactions = getDefaultLlvmModuleActions(),
|
||||
op = { context, _ -> runCoveragePass(context) }
|
||||
)
|
||||
|
||||
internal val RemoveRedundantSafepointsPhase = createSimpleNamedCompilerPhase<BitcodePostProcessingContext, Unit>(
|
||||
name = "RemoveRedundantSafepoints",
|
||||
description = "Remove function prologue safepoints inlined to another function",
|
||||
@@ -172,9 +164,6 @@ internal fun <T : BitcodePostProcessingContext> PhaseEngine<T>.runBitcodePostPro
|
||||
null -> {}
|
||||
}
|
||||
}
|
||||
if (context is NativeGenerationState && context.coverage.enabled) {
|
||||
newEngine(context) { it.runPhase(CoveragePhase) }
|
||||
}
|
||||
if (context.config.memoryModel == MemoryModel.EXPERIMENTAL) {
|
||||
runPhase(RemoveRedundantSafepointsPhase)
|
||||
}
|
||||
|
||||
+1
-3
@@ -19,7 +19,6 @@ internal data class LinkerPhaseInput(
|
||||
val dependenciesTrackingResult: DependenciesTrackingResult,
|
||||
val outputFiles: OutputFiles,
|
||||
val resolvedCacheBinaries: ResolvedCacheBinaries,
|
||||
val isCoverageEnabled: Boolean,
|
||||
)
|
||||
|
||||
internal val LinkerPhase = createSimpleNamedCompilerPhase<PhaseContext, LinkerPhaseInput>(
|
||||
@@ -29,7 +28,6 @@ internal val LinkerPhase = createSimpleNamedCompilerPhase<PhaseContext, LinkerPh
|
||||
val linker = Linker(
|
||||
config = context.config,
|
||||
linkerOutput = input.outputKind,
|
||||
isCoverageEnabled = input.isCoverageEnabled,
|
||||
outputFiles = input.outputFiles
|
||||
)
|
||||
val commands = linker.linkCommands(
|
||||
@@ -54,4 +52,4 @@ internal val PreLinkCachesPhase = createSimpleNamedCompilerPhase<PhaseContext, P
|
||||
val inputFiles = input.objectFiles.map { it.absoluteFile.normalize().path } + input.caches.static
|
||||
val commands = context.config.platform.linker.preLinkCommands(inputFiles, input.outputObjectFile.absoluteFile.normalize().path)
|
||||
runLinkerCommands(context, commands, cachingInvolved = true)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-5
@@ -110,7 +110,7 @@ internal fun <C : PhaseContext> PhaseEngine<C>.runBackend(backendContext: Contex
|
||||
depsFilePath.File().writeLines(DependenciesTrackingResult.serialize(dependenciesTrackingResult))
|
||||
}
|
||||
val moduleCompilationOutput = ModuleCompilationOutput(bitcodeFile, dependenciesTrackingResult)
|
||||
compileAndLink(moduleCompilationOutput, outputFiles.mainFileName, outputFiles, tempFiles, isCoverageEnabled = false)
|
||||
compileAndLink(moduleCompilationOutput, outputFiles.mainFileName, outputFiles, tempFiles)
|
||||
}
|
||||
} finally {
|
||||
tempFiles.dispose()
|
||||
@@ -163,7 +163,7 @@ internal fun <C : PhaseContext> PhaseEngine<C>.runBitcodeBackend(context: Bitcod
|
||||
bitcodeEngine.runBitcodePostProcessing()
|
||||
runPhase(WriteBitcodeFilePhase, WriteBitcodeFileInput(context.llvm.module, bitcodeFile))
|
||||
val moduleCompilationOutput = ModuleCompilationOutput(bitcodeFile, dependencies)
|
||||
compileAndLink(moduleCompilationOutput, outputFiles.mainFileName, outputFiles, tempFiles, isCoverageEnabled = false)
|
||||
compileAndLink(moduleCompilationOutput, outputFiles.mainFileName, outputFiles, tempFiles)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +269,6 @@ internal fun <C : PhaseContext> PhaseEngine<C>.compileAndLink(
|
||||
linkerOutputFile: String,
|
||||
outputFiles: OutputFiles,
|
||||
temporaryFiles: TempFiles,
|
||||
isCoverageEnabled: Boolean,
|
||||
) {
|
||||
val compilationResult = temporaryFiles.create(File(outputFiles.nativeBinaryFile).name, ".o").javaFile()
|
||||
runPhase(ObjectFilesPhase, ObjectFilesPhaseInput(moduleCompilationOutput.bitcodeFile, compilationResult))
|
||||
@@ -298,7 +297,6 @@ internal fun <C : PhaseContext> PhaseEngine<C>.compileAndLink(
|
||||
moduleCompilationOutput.dependenciesTrackingResult,
|
||||
outputFiles,
|
||||
cacheBinaries,
|
||||
isCoverageEnabled = isCoverageEnabled
|
||||
)
|
||||
runPhase(LinkerPhase, linkerPhaseInput)
|
||||
if (context.config.produce.isCache) {
|
||||
@@ -388,4 +386,4 @@ private fun PhaseEngine<NativeGenerationState>.findDependenciesToCompile(): List
|
||||
// This guarantees that libraries initializers are emitted in correct order.
|
||||
private fun mergeDependencies(targetModule: IrModuleFragment, dependencies: List<IrModuleFragment>) {
|
||||
targetModule.files.addAll(0, dependencies.flatMap { it.files })
|
||||
}
|
||||
}
|
||||
|
||||
-16
@@ -17,7 +17,6 @@ import org.jetbrains.kotlin.backend.konan.cexport.CAdapterExportedElements
|
||||
import org.jetbrains.kotlin.backend.konan.cgen.CBridgeOrigin
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.*
|
||||
import org.jetbrains.kotlin.backend.konan.ir.*
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.coverage.LLVMCoverageInstrumentation
|
||||
import org.jetbrains.kotlin.backend.konan.lower.*
|
||||
import org.jetbrains.kotlin.builtins.UnsignedType
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
@@ -472,8 +471,6 @@ internal class CodeGeneratorVisitor(
|
||||
override fun visitModuleFragment(declaration: IrModuleFragment) {
|
||||
context.log{"visitModule : ${ir2string(declaration)}"}
|
||||
|
||||
generationState.coverage.collectRegions(declaration)
|
||||
|
||||
initializeCachedBoxes(generationState)
|
||||
declaration.acceptChildrenVoid(this)
|
||||
|
||||
@@ -483,7 +480,6 @@ internal class CodeGeneratorVisitor(
|
||||
|
||||
codegen.objCDataGenerator?.finishModule()
|
||||
|
||||
generationState.coverage.writeRegionInfo()
|
||||
overrideRuntimeGlobals()
|
||||
appendLlvmUsed("llvm.used", llvm.usedFunctions.map { it.toConstPointer().llvm } + llvm.usedGlobals)
|
||||
appendLlvmUsed("llvm.compiler.used", llvm.compilerUsedGlobals)
|
||||
@@ -744,9 +740,6 @@ internal class CodeGeneratorVisitor(
|
||||
constructor(llvmFunction: LlvmCallable, functionGenerationContext: FunctionGenerationContext) :
|
||||
this(functionGenerationContext, null, llvmFunction)
|
||||
|
||||
val coverageInstrumentation: LLVMCoverageInstrumentation? =
|
||||
generationState.coverage.tryGetInstrumentation(declaration) { function, args -> functionGenerationContext.call(function, args) }
|
||||
|
||||
override fun genReturn(target: IrSymbolOwner, value: LLVMValueRef?) {
|
||||
if (declaration == null || target == declaration) {
|
||||
if ((target as IrFunction).returnsUnit()) {
|
||||
@@ -884,7 +877,6 @@ internal class CodeGeneratorVisitor(
|
||||
val parameterScope = ParameterScope(declaration, functionGenerationContext)
|
||||
using(parameterScope) usingParameterScope@{
|
||||
using(VariableScope()) usingVariableScope@{
|
||||
recordCoverage(body)
|
||||
if (declaration.isReifiedInline) {
|
||||
callDirect(context.ir.symbols.throwIllegalStateExceptionWithMessage.owner,
|
||||
listOf(codegen.staticData.kotlinStringLiteral(
|
||||
@@ -974,18 +966,10 @@ internal class CodeGeneratorVisitor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun recordCoverage(irElement: IrElement) {
|
||||
val scope = currentCodeContext.functionScope()
|
||||
if (scope is FunctionScope) {
|
||||
scope.coverageInstrumentation?.instrumentIrElement(irElement)
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------//
|
||||
|
||||
private fun evaluateExpression(value: IrExpression, resultSlot: LLVMValueRef? = null): LLVMValueRef {
|
||||
updateBuilderDebugLocation(value)
|
||||
recordCoverage(value)
|
||||
when (value) {
|
||||
is IrTypeOperatorCall -> return evaluateTypeOperator (value, resultSlot)
|
||||
is IrCall -> return evaluateCall (value, resultSlot)
|
||||
|
||||
+1
-5
@@ -27,10 +27,6 @@ class LlvmCallable(val functionType: LLVMTypeRef, private val llvmValue: LLVMVal
|
||||
LLVMCountParamTypes(functionType)
|
||||
}
|
||||
|
||||
val pgoFunctionNameVar by lazy {
|
||||
LLVMCreatePGOFunctionNameVar(llvmValue, name)!!
|
||||
}
|
||||
|
||||
val isConstant by lazy {
|
||||
LLVMIsConstant(llvmValue) == 1
|
||||
}
|
||||
@@ -82,4 +78,4 @@ class LlvmCallable(val functionType: LLVMTypeRef, private val llvmValue: LLVMVal
|
||||
// these functions are potentially unsafe, as they need to use same attribute provider when converted to callable
|
||||
internal fun toConstPointer() = constPointer(llvmValue)
|
||||
internal fun asCallback() = llvmValue
|
||||
}
|
||||
}
|
||||
|
||||
-95
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.backend.konan.llvm.coverage
|
||||
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.computeSymbolName
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.lineAndColumn
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||
import org.jetbrains.kotlin.ir.declarations.name
|
||||
import org.jetbrains.kotlin.ir.util.ir2string
|
||||
|
||||
/**
|
||||
* The most important class in the coverage package.
|
||||
* It describes textual region of the code that is associated with IrElement.
|
||||
* Besides the obvious [file] and line/column borders, it has [RegionKind] which is described later.
|
||||
*/
|
||||
class Region(
|
||||
val startOffset: Int,
|
||||
val endOffset: Int,
|
||||
val file: IrFile,
|
||||
val kind: RegionKind
|
||||
) {
|
||||
val startLineAndColumn: Pair<Int, Int>
|
||||
get() = file.fileEntry.lineAndColumn(startOffset)
|
||||
|
||||
val endLineAndColumn: Pair<Int, Int>
|
||||
get() = file.fileEntry.lineAndColumn(endOffset)
|
||||
|
||||
companion object {
|
||||
fun fromIr(irElement: IrElement, irFile: IrFile, kind: RegionKind = RegionKind.Code) =
|
||||
fromOffset(irElement.startOffset, irElement.endOffset, irFile, kind)
|
||||
|
||||
fun fromOffset(startOffset: Int, endOffset: Int, irFile: IrFile, kind: RegionKind = RegionKind.Code) =
|
||||
if (startOffset == UNDEFINED_OFFSET || endOffset == UNDEFINED_OFFSET || startOffset == endOffset) null
|
||||
else Region(startOffset, endOffset, irFile, kind)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val expansion = (kind as? RegionKind.Expansion)?.let { " expand to " + it.expandedFile.name } ?: ""
|
||||
return "${file.name}$expansion: ${kind::class.simpleName} $startLineAndColumn -> $endLineAndColumn"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes what is the given code region.
|
||||
* Based on llvm::coverage::CounterMappingRegion.
|
||||
* Currently only [RegionKind.Code] is used.
|
||||
*/
|
||||
sealed class RegionKind {
|
||||
/**
|
||||
* Regular peace of code.
|
||||
*/
|
||||
object Code : RegionKind()
|
||||
/**
|
||||
* Empty line.
|
||||
*/
|
||||
object Gap : RegionKind()
|
||||
/**
|
||||
* Region of code that is an expansion of another source file.
|
||||
* Used for inline function.
|
||||
*/
|
||||
class Expansion(val expandedFile: IrFile) : RegionKind()
|
||||
}
|
||||
|
||||
/**
|
||||
* "Regional" description of the [function].
|
||||
*/
|
||||
class FunctionRegions(
|
||||
val function: IrFunction,
|
||||
val regions: Map<IrElement, Region>
|
||||
) {
|
||||
// Enumeration is required for serialization and instrumentation calls.
|
||||
val regionEnumeration = regions.values.mapIndexed { index, region -> region to index }.toMap()
|
||||
// Actually, it should be computed.
|
||||
// But since we don't support PGO structural hash doesn't really matter for now.
|
||||
val structuralHash: Long = 0
|
||||
|
||||
override fun toString(): String = buildString {
|
||||
appendLine("${function.computeSymbolName()} regions:")
|
||||
regions.forEach { (irElem, region) -> appendLine("${ir2string(irElem)} -> ($region)") }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Since file is the biggest unit in terms of the code coverage
|
||||
* we aggregate [FunctionRegions] per [file].
|
||||
*/
|
||||
class FileRegionInfo(
|
||||
val file: IrFile,
|
||||
val functions: List<FunctionRegions>
|
||||
)
|
||||
-137
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.backend.konan.llvm.coverage
|
||||
|
||||
import llvm.*
|
||||
import org.jetbrains.kotlin.backend.konan.*
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.LlvmCallable
|
||||
import org.jetbrains.kotlin.backend.konan.reportCompilationError
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||
import org.jetbrains.kotlin.konan.file.File
|
||||
import org.jetbrains.kotlin.konan.target.supportsCodeCoverage
|
||||
|
||||
/**
|
||||
* "Umbrella" class of all the of the code coverage related logic.
|
||||
*/
|
||||
internal class CoverageManager(val generationState: NativeGenerationState) {
|
||||
private val context = generationState.context
|
||||
private val config = generationState.config
|
||||
|
||||
private val shouldCoverSources: Boolean =
|
||||
config.shouldCoverSources
|
||||
|
||||
private val librariesToCover: Set<String> =
|
||||
config.resolve.coveredLibraries.map { it.libraryName }.toSet()
|
||||
|
||||
private val llvmProfileFilenameGlobal = "__llvm_profile_filename"
|
||||
|
||||
private val defaultOutputFilePath: String by lazy {
|
||||
"${generationState.outputFile}.profraw"
|
||||
}
|
||||
|
||||
private val outputFileName: String =
|
||||
config.configuration.get(KonanConfigKeys.PROFRAW_PATH)
|
||||
?.let { File(it).absolutePath }
|
||||
?: defaultOutputFilePath
|
||||
|
||||
val enabled: Boolean =
|
||||
shouldCoverSources || librariesToCover.isNotEmpty()
|
||||
|
||||
init {
|
||||
if (enabled && !checkRestrictions()) {
|
||||
generationState.reportCompilationError("Coverage is not supported for ${config.target}.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkRestrictions(): Boolean {
|
||||
val isKindAllowed = config.produce.involvesBitcodeGeneration
|
||||
val target = config.target
|
||||
val isTargetAllowed = target.supportsCodeCoverage()
|
||||
return isKindAllowed && isTargetAllowed
|
||||
}
|
||||
|
||||
private val filesRegionsInfo = mutableListOf<FileRegionInfo>()
|
||||
|
||||
private fun getFunctionRegions(irFunction: IrFunction) =
|
||||
filesRegionsInfo.flatMap { it.functions }.firstOrNull { it.function == irFunction }
|
||||
|
||||
private val coveredModules: Set<ModuleDescriptor> by lazy {
|
||||
val coveredSources = if (shouldCoverSources) {
|
||||
context.sourcesModules
|
||||
} else {
|
||||
emptySet()
|
||||
}
|
||||
val coveredLibs = context.irModules.filter { it.key in librariesToCover }.values
|
||||
.map { it.descriptor }.toSet()
|
||||
coveredLibs + coveredSources
|
||||
}
|
||||
|
||||
@OptIn(ObsoleteDescriptorBasedAPI::class)
|
||||
private fun fileCoverageFilter(file: IrFile) =
|
||||
file.moduleDescriptor in coveredModules
|
||||
|
||||
/**
|
||||
* Walk [irModuleFragment] subtree and collect [FileRegionInfo] for files that are part of [coveredModules].
|
||||
*/
|
||||
fun collectRegions(irModuleFragment: IrModuleFragment) {
|
||||
if (enabled) {
|
||||
val regions = CoverageRegionCollector(this::fileCoverageFilter).collectFunctionRegions(irModuleFragment)
|
||||
filesRegionsInfo += regions
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return [LLVMCoverageInstrumentation] instance if [irFunction] should be covered.
|
||||
*/
|
||||
fun tryGetInstrumentation(irFunction: IrFunction?, callSitePlacer: (function: LlvmCallable, args: List<LLVMValueRef>) -> Unit) =
|
||||
if (enabled && irFunction != null) {
|
||||
getFunctionRegions(irFunction)?.let { LLVMCoverageInstrumentation(generationState, it, callSitePlacer) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
/**
|
||||
* Add __llvm_coverage_mapping to the LLVM module.
|
||||
*/
|
||||
fun writeRegionInfo() {
|
||||
if (enabled) {
|
||||
LLVMCoverageWriter(generationState, filesRegionsInfo).write()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add passes that should be executed after main LLVM optimization pipeline.
|
||||
*/
|
||||
fun addLateLlvmPasses(passManager: LLVMPassManagerRef) {
|
||||
if (enabled) {
|
||||
// It's a late pass since DCE can kill __llvm_profile_filename global.
|
||||
LLVMAddInstrProfPass(passManager, outputFileName)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Since we performing instruction profiling before internalization and global dce
|
||||
* __llvm_profile_filename need to be added to exported symbols.
|
||||
*/
|
||||
fun addExportedSymbols(): List<String> =
|
||||
if (enabled) {
|
||||
listOf(llvmProfileFilenameGlobal)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun runCoveragePass(generationState: NativeGenerationState) {
|
||||
if (!generationState.coverage.enabled) return
|
||||
val passManager = LLVMCreatePassManager()!!
|
||||
LLVMKotlinAddTargetLibraryInfoWrapperPass(passManager, generationState.llvm.targetTriple)
|
||||
generationState.coverage.addLateLlvmPasses(passManager)
|
||||
LLVMRunPassManager(passManager, generationState.llvm.module)
|
||||
LLVMDisposePassManager(passManager)
|
||||
}
|
||||
-222
@@ -1,222 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.backend.konan.llvm.coverage
|
||||
|
||||
import org.jetbrains.kotlin.ir.util.sourceFileSymbol
|
||||
import org.jetbrains.kotlin.backend.common.pop
|
||||
import org.jetbrains.kotlin.backend.common.push
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.IrStatement
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.util.statements
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||
|
||||
/**
|
||||
* Collect all regions in the module.
|
||||
* @param fileFilter filters files that should be processed.
|
||||
*/
|
||||
internal class CoverageRegionCollector(private val fileFilter: (IrFile) -> Boolean) {
|
||||
|
||||
fun collectFunctionRegions(irModuleFragment: IrModuleFragment): List<FileRegionInfo> =
|
||||
irModuleFragment.files
|
||||
.filter(fileFilter)
|
||||
.map { file ->
|
||||
val collector = FunctionsCollector(file)
|
||||
collector.visitFile(file)
|
||||
FileRegionInfo(file, collector.functionRegions)
|
||||
}
|
||||
|
||||
private inner class FunctionsCollector(val file: IrFile) : IrElementVisitorVoid {
|
||||
|
||||
val functionRegions = mutableListOf<FunctionRegions>()
|
||||
|
||||
override fun visitElement(element: IrElement) {
|
||||
element.acceptChildrenVoid(this)
|
||||
}
|
||||
|
||||
override fun visitFunction(declaration: IrFunction) {
|
||||
if (!declaration.isInline && !declaration.isExternal && !declaration.isGeneratedByCompiler) {
|
||||
val regionsCollector = IrFunctionRegionsCollector(fileFilter, file)
|
||||
declaration.acceptVoid(regionsCollector)
|
||||
if (regionsCollector.regions.isNotEmpty()) {
|
||||
functionRegions += FunctionRegions(declaration, regionsCollector.regions)
|
||||
}
|
||||
}
|
||||
// TODO: Decide how to work with local functions. Should they be process separately?
|
||||
declaration.acceptChildrenVoid(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// User doesn't bother about compiler-generated declarations.
|
||||
// So lets filter them.
|
||||
private val IrFunction.isGeneratedByCompiler: Boolean
|
||||
get() {
|
||||
return origin != IrDeclarationOrigin.DEFINED || name.asString() == "Konan_start"
|
||||
}
|
||||
|
||||
/**
|
||||
* Very similar to [org.jetbrains.kotlin.backend.konan.llvm.CodeGeneratorVisitor] but instead of bitcode generation we collect regions.
|
||||
* [fileFilter]: specify which files should be processed by code coverage. Here it is required
|
||||
* for checking calls to inline functions from other files.
|
||||
* TODO: for now it is very inaccurate.
|
||||
*/
|
||||
private class IrFunctionRegionsCollector(
|
||||
val fileFilter: (IrFile) -> Boolean,
|
||||
val irFile: IrFile
|
||||
) : IrElementVisitorVoid {
|
||||
|
||||
private data class StatementContext(val current: IrStatement, val next: IrStatement?)
|
||||
|
||||
val regions = mutableMapOf<IrElement, Region>()
|
||||
|
||||
private val irFileStack = mutableListOf(irFile)
|
||||
|
||||
private val regionStack = mutableListOf<Region>()
|
||||
|
||||
private val irStatementsStack = mutableListOf<StatementContext>()
|
||||
|
||||
private val currentFile: IrFile
|
||||
get() = irFileStack.last()
|
||||
|
||||
private val currentRegion: Region
|
||||
get() = regionStack.last()
|
||||
|
||||
override fun visitElement(element: IrElement) {
|
||||
element.acceptChildrenVoid(this)
|
||||
}
|
||||
|
||||
override fun visitSimpleFunction(declaration: IrSimpleFunction) {
|
||||
declaration.body?.let { body ->
|
||||
visitInRegionContext(recordRegion(body) ?: return) {
|
||||
body.acceptVoid(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitConstructor(declaration: IrConstructor) {
|
||||
val statements = declaration.body?.statements ?: return
|
||||
visitInStatementContext(statements) { statement ->
|
||||
if (statement is IrDelegatingConstructorCall && !declaration.isPrimary
|
||||
|| statement !is IrDelegatingConstructorCall && statement !is IrReturn) {
|
||||
recordRegion(statement)
|
||||
statement.acceptVoid(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitBody(body: IrBody) = visitInStatementContext(body.statements)
|
||||
|
||||
override fun visitContainerExpression(expression: IrContainerExpression) {
|
||||
val statements = expression.statements
|
||||
when (expression) {
|
||||
is IrReturnableBlock -> {
|
||||
val file = expression.sourceFileSymbol?.owner
|
||||
if (file != null && file != currentFile && fileFilter(file)) {
|
||||
recordRegion(expression)
|
||||
visitInFileContext(file) {
|
||||
visitInStatementContext(statements)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> visitInStatementContext(statements)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: The following implementation produces correct region mapping, but something goes wrong later
|
||||
// override fun visitFieldAccess(expression: IrFieldAccessExpression) {
|
||||
// expression.receiver?.let { recordRegion(it) }
|
||||
// expression.acceptChildrenVoid(this)
|
||||
// }
|
||||
|
||||
override fun visitWhen(expression: IrWhen) {
|
||||
val branches = expression.branches
|
||||
branches.forEach {
|
||||
val condition = it.condition
|
||||
val result = it.result
|
||||
|
||||
if (condition is IrConst<*> && condition.value == true && condition.endOffset == result.endOffset) {
|
||||
// Probably an 'else' branch.
|
||||
// Note: can't rely on [IrElseBranch], because IR deserializer doesn't emit it.
|
||||
recordRegion(result)
|
||||
} else {
|
||||
recordRegion(condition)
|
||||
recordRegion(result, condition.endOffset, result.endOffset)
|
||||
condition.acceptVoid(this)
|
||||
}
|
||||
result.acceptVoid(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitLoop(loop: IrLoop) {
|
||||
val condition = loop.condition
|
||||
recordRegion(condition)
|
||||
condition.acceptVoid(this)
|
||||
|
||||
val body = loop.body ?: return
|
||||
when (loop) {
|
||||
is IrWhileLoop -> recordRegion(body, condition.endOffset, body.endOffset)
|
||||
is IrDoWhileLoop -> recordRegion(body, body.startOffset, condition.startOffset)
|
||||
}
|
||||
body.acceptVoid(this)
|
||||
}
|
||||
|
||||
override fun visitBreakContinue(jump: IrBreakContinue) {
|
||||
val (current, next) = irStatementsStack.lastOrNull() ?: return
|
||||
recordRegion(next ?: return, current.endOffset, jump.loop.endOffset)
|
||||
}
|
||||
|
||||
override fun visitReturn(expression: IrReturn) {
|
||||
irStatementsStack.subList(0, irStatementsStack.lastIndex)
|
||||
.filter { (current, next) -> next != null && current.endOffset > expression.endOffset }
|
||||
.forEach { (current, next) -> recordRegion(next!!, expression.endOffset, current.endOffset) }
|
||||
val next = irStatementsStack.lastOrNull()?.next ?: return
|
||||
val nextRegion = recordRegion(next, expression.endOffset, currentRegion.endOffset) ?: return
|
||||
regionStack.pop()
|
||||
regionStack.push(nextRegion)
|
||||
}
|
||||
|
||||
private fun visitInFileContext(file: IrFile, visit: () -> Unit) {
|
||||
irFileStack.push(file)
|
||||
visit()
|
||||
irFileStack.pop()
|
||||
}
|
||||
|
||||
private fun visitInRegionContext(region: Region, visit: () -> Unit) {
|
||||
regionStack.push(region)
|
||||
visit()
|
||||
regionStack.pop()
|
||||
}
|
||||
|
||||
private fun visitInStatementContext(
|
||||
statements: List<IrStatement>,
|
||||
visit: (IrStatement) -> Unit = { statement -> statement.acceptVoid(this) }
|
||||
) {
|
||||
for (i in 0..statements.lastIndex) {
|
||||
val current = statements[i]
|
||||
if (!current.hasValidOffsets()) {
|
||||
continue
|
||||
}
|
||||
val nextInContext = irStatementsStack.lastOrNull()?.next
|
||||
val next = if (i < statements.lastIndex && statements[i + 1].hasValidOffsets()) statements[i + 1] else nextInContext
|
||||
irStatementsStack.push(StatementContext(current, next))
|
||||
visit(current)
|
||||
irStatementsStack.pop()
|
||||
}
|
||||
}
|
||||
|
||||
private fun recordRegion(irElement: IrElement, kind: RegionKind = RegionKind.Code)
|
||||
= Region.fromIr(irElement, currentFile, kind)?.also { regions[irElement] = it }
|
||||
|
||||
private fun recordRegion(irElement: IrElement, startOffset: Int, endOffset: Int, kind: RegionKind = RegionKind.Code)
|
||||
= Region.fromOffset(startOffset, endOffset, currentFile, kind)?.also { regions[irElement] = it }
|
||||
|
||||
private fun IrElement.hasValidOffsets() = startOffset != UNDEFINED_OFFSET && endOffset != UNDEFINED_OFFSET
|
||||
&& startOffset != endOffset
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.backend.konan.llvm.coverage
|
||||
|
||||
import llvm.*
|
||||
import org.jetbrains.kotlin.backend.konan.NativeGenerationState
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.*
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||
|
||||
/**
|
||||
* Places calls to `llvm.instrprof.increment` in the beginning of the each
|
||||
* region in [functionRegions].
|
||||
*/
|
||||
internal class LLVMCoverageInstrumentation(
|
||||
override val generationState: NativeGenerationState,
|
||||
private val functionRegions: FunctionRegions,
|
||||
private val callSitePlacer: (function: LlvmCallable, args: List<LLVMValueRef>) -> Unit
|
||||
) : ContextUtils {
|
||||
|
||||
private val functionNameGlobal = createFunctionNameGlobal(functionRegions.function)
|
||||
|
||||
private val functionHash = llvm.int64(functionRegions.structuralHash)
|
||||
|
||||
private val instrProfIncrement by lazy {
|
||||
val incrementFun = LLVMInstrProfIncrement(llvm.module)!!
|
||||
LlvmCallable(
|
||||
getGlobalFunctionType(incrementFun),
|
||||
incrementFun,
|
||||
LlvmFunctionAttributeProvider.copyFromExternal(incrementFun)
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: It's a great place for some debug output.
|
||||
fun instrumentIrElement(element: IrElement) {
|
||||
functionRegions.regions[element]?.let {
|
||||
placeRegionIncrement(it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See https://llvm.org/docs/LangRef.html#llvm-instrprof-increment-intrinsic
|
||||
*/
|
||||
private fun placeRegionIncrement(region: Region) {
|
||||
val numberOfRegions = llvm.int32(functionRegions.regions.size)
|
||||
val regionNumber = llvm.int32(functionRegions.regionEnumeration.getValue(region))
|
||||
val args = listOf(functionNameGlobal, functionHash, numberOfRegions, regionNumber)
|
||||
callSitePlacer(instrProfIncrement, args)
|
||||
}
|
||||
|
||||
// Each profiled function should have a global with its name in a specific format.
|
||||
private fun createFunctionNameGlobal(function: IrFunction): LLVMValueRef {
|
||||
val pgoFunctionName = function.llvmFunction.pgoFunctionNameVar
|
||||
return LLVMConstBitCast(pgoFunctionName, llvm.int8PtrType)!!
|
||||
}
|
||||
}
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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.backend.konan.llvm.coverage
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import llvm.*
|
||||
import org.jetbrains.kotlin.backend.konan.NativeGenerationState
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.declarations.path
|
||||
import org.jetbrains.kotlin.konan.file.File
|
||||
|
||||
private fun RegionKind.toLLVMCoverageRegionKind(): LLVMCoverageRegionKind = when (this) {
|
||||
RegionKind.Code -> LLVMCoverageRegionKind.CODE
|
||||
RegionKind.Gap -> LLVMCoverageRegionKind.GAP
|
||||
is RegionKind.Expansion -> LLVMCoverageRegionKind.EXPANSION
|
||||
}
|
||||
|
||||
private fun LLVMCoverageRegion.populateFrom(region: Region, regionId: Int, filesIndex: Map<IrFile, Int>) = apply {
|
||||
val (lineStart, columnStart) = region.startLineAndColumn
|
||||
val (lineEnd, columnEnd) = region.endLineAndColumn
|
||||
|
||||
fileId = filesIndex.getValue(region.file)
|
||||
this.lineStart = lineStart
|
||||
this.columnStart = columnStart
|
||||
this.lineEnd = lineEnd
|
||||
this.columnEnd = columnEnd
|
||||
counterId = regionId
|
||||
kind = region.kind.toLLVMCoverageRegionKind()
|
||||
expandedFileId = if (region.kind is RegionKind.Expansion) filesIndex.getValue(region.kind.expandedFile) else 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes all of the coverage information to the [org.jetbrains.kotlin.backend.konan.NativeGenerationState.llvm.module].
|
||||
* See http://llvm.org/docs/CoverageMappingFormat.html for the format description.
|
||||
*/
|
||||
internal class LLVMCoverageWriter(
|
||||
private val generationState: NativeGenerationState,
|
||||
private val filesRegionsInfo: List<FileRegionInfo>
|
||||
) {
|
||||
fun write() {
|
||||
if (filesRegionsInfo.isEmpty()) return
|
||||
|
||||
val module = generationState.llvm.module
|
||||
val filesIndex = filesRegionsInfo.mapIndexed { index, fileRegionInfo -> fileRegionInfo.file to index }.toMap()
|
||||
|
||||
val coverageGlobal = memScoped {
|
||||
val (functionMappingRecords, functionCoverages) = filesRegionsInfo.flatMap { it.functions }.map { functionRegions ->
|
||||
val regions = (functionRegions.regions.values).map { region ->
|
||||
alloc<LLVMCoverageRegion>().populateFrom(region, functionRegions.regionEnumeration.getValue(region), filesIndex).ptr
|
||||
}
|
||||
val fileIds = functionRegions.regions.map { filesIndex.getValue(it.value.file) }.toSet().toIntArray()
|
||||
val functionCoverage = LLVMWriteCoverageRegionMapping(
|
||||
fileIds.toCValues(), fileIds.size.signExtend(),
|
||||
regions.toCValues(), regions.size.signExtend())
|
||||
|
||||
val functionName = generationState.llvmDeclarations.forFunction(functionRegions.function).name
|
||||
val functionMappingRecord = LLVMAddFunctionMappingRecord(LLVMGetModuleContext(generationState.llvm.module),
|
||||
functionName, functionRegions.structuralHash, functionCoverage)!!
|
||||
|
||||
Pair(functionMappingRecord, functionCoverage)
|
||||
}.unzip()
|
||||
val (filenames, fileIds) = filesIndex.entries.toList().map { File(it.key.path).absolutePath to it.value }.unzip()
|
||||
val retval = LLVMCoverageEmit(module, functionMappingRecords.toCValues(), functionMappingRecords.size.signExtend(),
|
||||
filenames.toCStringArray(this), fileIds.toIntArray().toCValues(), fileIds.size.signExtend(),
|
||||
functionCoverages.map { it }.toCValues(), functionCoverages.size.signExtend())!!
|
||||
|
||||
// TODO: Is there a better way to cleanup fields of T* type in `memScoped`?
|
||||
functionCoverages.forEach { LLVMFunctionCoverageDispose(it) }
|
||||
|
||||
retval
|
||||
}
|
||||
generationState.llvm.usedGlobals.add(coverageGlobal)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
headers = llvm-c/Core.h llvm-c/Target.h llvm-c/Analysis.h llvm-c/BitWriter.h \
|
||||
llvm-c/BitReader.h llvm-c/Transforms/PassManagerBuilder.h llvm-c/Transforms/IPO.h \
|
||||
llvm-c/TargetMachine.h llvm-c/Target.h llvm-c/Linker.h llvm-c/Initialization.h \
|
||||
llvm-c/DebugInfo.h DebugInfoC.h CoverageMappingC.h CAPIExtensions.h RemoveRedundantSafepoints.h OpaquePointerAPI.h
|
||||
llvm-c/DebugInfo.h DebugInfoC.h CAPIExtensions.h RemoveRedundantSafepoints.h OpaquePointerAPI.h
|
||||
|
||||
headerFilter = llvm-c/* llvm-c/**/* DebugInfoC.h CoverageMappingC.h CAPIExtensions.h RemoveRedundantSafepoints.h OpaquePointerAPI.h
|
||||
headerFilter = llvm-c/* llvm-c/**/* DebugInfoC.h CAPIExtensions.h RemoveRedundantSafepoints.h OpaquePointerAPI.h
|
||||
|
||||
compilerOpts = -std=c99 \
|
||||
-Wall -W -Wno-unused-parameter -Wwrite-strings -Wmissing-field-initializers \
|
||||
@@ -20,7 +20,7 @@ linkerOpts = -fvisibility-inlines-hidden \
|
||||
-DNDEBUG -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS \
|
||||
-ldebugInfo -lllvmext
|
||||
|
||||
# ./llvm-config --libs analysis bitreader bitwriter core linker target coverage analysis ipo instrumentation lto objcarcopts arm aarch64 webassembly x86 mips
|
||||
# ./llvm-config --libs analysis bitreader bitwriter core linker target analysis ipo instrumentation lto objcarcopts arm aarch64 webassembly x86 mips
|
||||
linkerOpts.osx = \
|
||||
-Wl,-search_paths_first -Wl,-headerpad_max_install_names \
|
||||
-lpthread -lz -lm -lcurses -Wl,-U,_futimens -Wl,-U,_LLVMDumpType \
|
||||
@@ -31,7 +31,7 @@ linkerOpts.osx = \
|
||||
-lLLVMAArch64Utils -lLLVMAArch64Info -lLLVMARMDisassembler -lLLVMARMCodeGen -lLLVMCFGuard -lLLVMGlobalISel -lLLVMSelectionDAG \
|
||||
-lLLVMAsmPrinter -lLLVMDebugInfoDWARF -lLLVMARMAsmParser -lLLVMARMDesc -lLLVMMCDisassembler -lLLVMARMUtils -lLLVMARMInfo -lLLVMLTO \
|
||||
-lLLVMPasses -lLLVMCoroutines -lLLVMObjCARCOpts -lLLVMExtensions -lLLVMCodeGen -lLLVMipo -lLLVMInstrumentation -lLLVMVectorize \
|
||||
-lLLVMScalarOpts -lLLVMIRReader -lLLVMAsmParser -lLLVMInstCombine -lLLVMFrontendOpenMP -lLLVMAggressiveInstCombine -lLLVMCoverage \
|
||||
-lLLVMScalarOpts -lLLVMIRReader -lLLVMAsmParser -lLLVMInstCombine -lLLVMFrontendOpenMP -lLLVMAggressiveInstCombine \
|
||||
-lLLVMTarget -lLLVMLinker -lLLVMTransformUtils -lLLVMBitWriter -lLLVMAnalysis -lLLVMProfileData -lLLVMObject -lLLVMTextAPI -lLLVMMCParser \
|
||||
-lLLVMMC -lLLVMDebugInfoCodeView -lLLVMDebugInfoMSF -lLLVMBitReader -lLLVMCore -lLLVMRemarks -lLLVMBitstreamReader -lLLVMBinaryFormat \
|
||||
-lLLVMSupport -lLLVMDemangle
|
||||
@@ -39,7 +39,7 @@ linkerOpts.osx = \
|
||||
|
||||
|
||||
|
||||
# ./llvm-config --libs analysis bitreader bitwriter core linker target coverage analysis ipo instrumentation lto arm aarch64 webassembly x86 mips
|
||||
# ./llvm-config --libs analysis bitreader bitwriter core linker target analysis ipo instrumentation lto arm aarch64 webassembly x86 mips
|
||||
linkerOpts.linux = \
|
||||
-Wl,-z,noexecstack \
|
||||
-lrt -ldl -lpthread -lz -lm \
|
||||
@@ -49,12 +49,12 @@ linkerOpts.linux = \
|
||||
-lLLVMAArch64Utils -lLLVMAArch64Info -lLLVMARMDisassembler -lLLVMARMCodeGen -lLLVMCFGuard -lLLVMGlobalISel -lLLVMSelectionDAG \
|
||||
-lLLVMAsmPrinter -lLLVMDebugInfoDWARF -lLLVMARMAsmParser -lLLVMARMDesc -lLLVMMCDisassembler -lLLVMARMUtils -lLLVMARMInfo -lLLVMLTO \
|
||||
-lLLVMPasses -lLLVMCoroutines -lLLVMObjCARCOpts -lLLVMExtensions -lLLVMCodeGen -lLLVMipo -lLLVMInstrumentation -lLLVMVectorize \
|
||||
-lLLVMScalarOpts -lLLVMIRReader -lLLVMAsmParser -lLLVMInstCombine -lLLVMFrontendOpenMP -lLLVMAggressiveInstCombine -lLLVMCoverage \
|
||||
-lLLVMScalarOpts -lLLVMIRReader -lLLVMAsmParser -lLLVMInstCombine -lLLVMFrontendOpenMP -lLLVMAggressiveInstCombine \
|
||||
-lLLVMTarget -lLLVMLinker -lLLVMTransformUtils -lLLVMBitWriter -lLLVMAnalysis -lLLVMProfileData -lLLVMObject -lLLVMTextAPI -lLLVMMCParser \
|
||||
-lLLVMMC -lLLVMDebugInfoCodeView -lLLVMDebugInfoMSF -lLLVMBitReader -lLLVMCore -lLLVMRemarks -lLLVMBitstreamReader -lLLVMBinaryFormat \
|
||||
-lLLVMSupport -lLLVMDemangle
|
||||
|
||||
# ./llvm-config --libs analysis bitreader bitwriter core linker target coverage analysis ipo instrumentation lto arm aarch64 webassembly x86
|
||||
# ./llvm-config --libs analysis bitreader bitwriter core linker target analysis ipo instrumentation lto arm aarch64 webassembly x86
|
||||
linkerOpts.mingw = \
|
||||
-lLLVMX86Disassembler -lLLVMX86AsmParser -lLLVMX86CodeGen -lLLVMX86Desc -lLLVMX86Info -lLLVMWebAssemblyDisassembler \
|
||||
-lLLVMWebAssemblyCodeGen -lLLVMWebAssemblyDesc -lLLVMWebAssemblyAsmParser -lLLVMWebAssemblyInfo -lLLVMAArch64Disassembler \
|
||||
@@ -62,7 +62,7 @@ linkerOpts.mingw = \
|
||||
-lLLVMARMCodeGen -lLLVMCFGuard -lLLVMGlobalISel -lLLVMSelectionDAG -lLLVMAsmPrinter -lLLVMDebugInfoDWARF -lLLVMARMAsmParser -lLLVMARMDesc \
|
||||
-lLLVMMCDisassembler -lLLVMARMUtils -lLLVMARMInfo -lLLVMLTO -lLLVMPasses -lLLVMCoroutines -lLLVMObjCARCOpts -lLLVMExtensions -lLLVMCodeGen \
|
||||
-lLLVMipo -lLLVMInstrumentation -lLLVMVectorize -lLLVMScalarOpts -lLLVMIRReader -lLLVMAsmParser -lLLVMInstCombine -lLLVMFrontendOpenMP \
|
||||
-lLLVMAggressiveInstCombine -lLLVMCoverage -lLLVMTarget -lLLVMLinker -lLLVMTransformUtils -lLLVMBitWriter -lLLVMAnalysis -lLLVMProfileData \
|
||||
-lLLVMAggressiveInstCombine -lLLVMTarget -lLLVMLinker -lLLVMTransformUtils -lLLVMBitWriter -lLLVMAnalysis -lLLVMProfileData \
|
||||
-lLLVMObject -lLLVMTextAPI -lLLVMMCParser -lLLVMMC -lLLVMDebugInfoCodeView -lLLVMDebugInfoMSF -lLLVMBitReader -lLLVMCore -lLLVMRemarks \
|
||||
-lLLVMBitstreamReader -lLLVMBinaryFormat -lLLVMSupport -lLLVMDemangle \
|
||||
-lpsapi -lshell32 -lole32 -luuid -ladvapi32
|
||||
|
||||
@@ -123,9 +123,6 @@ allprojects {
|
||||
// backend.native/tests/framework
|
||||
ext.testOutputFramework = rootProject.file("$testOutputRoot/framework")
|
||||
|
||||
// backent.native/tests/coverage
|
||||
ext.testOutputCoverage = rootProject.file("$testOutputRoot/coverage")
|
||||
|
||||
ext.testOutputFileCheck = rootProject.file("$testOutputRoot/filecheck")
|
||||
}
|
||||
testOutputExternal.mkdirs()
|
||||
@@ -187,7 +184,6 @@ tasks.named("run") {
|
||||
dependsOn(tasks.withType(FrameworkTest).matching { it.enabled })
|
||||
// Add regular gradle test tasks
|
||||
dependsOn(tasks.withType(Test).matching { it.enabled })
|
||||
dependsOn(tasks.withType(CoverageTest).matching { it.enabled })
|
||||
dependsOn(tasks.withType(FileCheckTest).matching { it.enabled })
|
||||
dependsOn(":kotlin-native:Interop:Indexer:check")
|
||||
dependsOn(":kotlin-native:Interop:StubGenerator:check")
|
||||
@@ -5080,112 +5076,6 @@ if (isAppleTarget(project)) {
|
||||
}
|
||||
}
|
||||
|
||||
if (UtilsKt.getTestTargetSupportsCodeCoverage(project)) {
|
||||
tasks.register("coverage_basic_program", CoverageTest) {
|
||||
|
||||
binaryName = "CoverageBasic"
|
||||
numberOfCoveredFunctions = 1
|
||||
numberOfCoveredLines = 3
|
||||
|
||||
konanArtifacts {
|
||||
program(binaryName, targets: [ target ]) {
|
||||
srcDir 'coverage/basic/program'
|
||||
baseDir "$testOutputCoverage/$binaryName"
|
||||
entryPoint "coverage.basic.program.main"
|
||||
extraOpts "-Xcoverage", "-Xcoverage-file=$profrawFile"
|
||||
extraOpts project.globalTestArgs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("coverage_basic_library", CoverageTest) {
|
||||
|
||||
binaryName = "CoverageBasicLibrary"
|
||||
numberOfCoveredFunctions = 1
|
||||
numberOfCoveredLines = 1
|
||||
|
||||
konanArtifacts {
|
||||
library("lib_to_cover", targets: [target.name]) {
|
||||
srcFiles 'coverage/basic/library/library.kt'
|
||||
}
|
||||
program(binaryName, targets: [ target ]) {
|
||||
srcFiles 'coverage/basic/library/main.kt'
|
||||
libraries {
|
||||
artifact konanArtifacts.lib_to_cover
|
||||
}
|
||||
baseDir "$testOutputCoverage/$binaryName"
|
||||
entryPoint "coverage.basic.library.main"
|
||||
extraOpts "-Xcoverage-file=$profrawFile", "-Xlibrary-to-cover=${konanArtifacts.lib_to_cover.getArtifactByTarget(target.name)}"
|
||||
extraOpts project.globalTestArgs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("coverage_controlflow", CoverageTest) {
|
||||
binaryName = "CoverageControlFlow"
|
||||
numberOfCoveredFunctions = 1
|
||||
numberOfCoveredLines = 102
|
||||
|
||||
konanArtifacts {
|
||||
program(binaryName, targets: [ target ]) {
|
||||
srcDir 'coverage/basic/controlflow'
|
||||
baseDir "$testOutputCoverage/$binaryName"
|
||||
entryPoint "coverage.basic.controlflow.main"
|
||||
extraOpts "-Xcoverage", "-Xcoverage-file=$profrawFile"
|
||||
extraOpts project.globalTestArgs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("coverage_jumps", CoverageTest) {
|
||||
binaryName = "CoverageJumps"
|
||||
numberOfCoveredFunctions = 8
|
||||
numberOfCoveredLines = 74
|
||||
|
||||
konanArtifacts {
|
||||
program(binaryName, targets: [ target ]) {
|
||||
srcDir 'coverage/basic/jumps'
|
||||
baseDir "$testOutputCoverage/$binaryName"
|
||||
entryPoint "coverage.basic.jumps.main"
|
||||
extraOpts "-Xcoverage", "-Xcoverage-file=$profrawFile"
|
||||
extraOpts project.globalTestArgs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("coverage_smoke0", CoverageTest) {
|
||||
binaryName = "CoverageSmoke0"
|
||||
numberOfCoveredFunctions = 2
|
||||
numberOfCoveredLines = 5
|
||||
|
||||
konanArtifacts {
|
||||
program(binaryName, targets: [ target ]) {
|
||||
srcDir 'coverage/basic/smoke0'
|
||||
baseDir "$testOutputCoverage/$binaryName"
|
||||
entryPoint "coverage.basic.smoke0.main"
|
||||
extraOpts "-Xcoverage", "-Xcoverage-file=$profrawFile"
|
||||
extraOpts project.globalTestArgs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("coverage_smoke1", CoverageTest) {
|
||||
binaryName = "CoverageSmoke1"
|
||||
numberOfCoveredFunctions = 9
|
||||
numberOfCoveredLines = 33
|
||||
|
||||
konanArtifacts {
|
||||
program(binaryName, targets: [ target ]) {
|
||||
srcDir 'coverage/basic/smoke1'
|
||||
baseDir "$testOutputCoverage/$binaryName"
|
||||
entryPoint "coverage.basic.smoke1.main"
|
||||
extraOpts "-Xcoverage", "-Xcoverage-file=$profrawFile"
|
||||
extraOpts project.globalTestArgs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
standaloneTest("local_ea_arraysfieldwrite") {
|
||||
disabled = (cacheTesting != null) // Cache is not compatible with -opt.
|
||||
useGoldenData = true
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
package coverage.basic.controlflow
|
||||
|
||||
fun main() {
|
||||
|
||||
// If Expression
|
||||
|
||||
var a = 1
|
||||
var b = 2
|
||||
if (a < b) println("a < b")
|
||||
|
||||
if (a > b) {
|
||||
println("a > b")
|
||||
} else if (a == b) {
|
||||
println("a == b")
|
||||
} else {
|
||||
println("a < b")
|
||||
}
|
||||
|
||||
if (a < b) {
|
||||
println("a < b")
|
||||
}
|
||||
else
|
||||
{
|
||||
println("a >= b")
|
||||
}
|
||||
|
||||
var max = if (a > b) a else b
|
||||
|
||||
max = if (a > b) {
|
||||
println("Choose a")
|
||||
a
|
||||
} else {
|
||||
println("Choose b")
|
||||
b
|
||||
}
|
||||
|
||||
if (a < b)
|
||||
|
||||
println("a < b")
|
||||
else
|
||||
|
||||
println("a >= b")
|
||||
|
||||
if (a > b)
|
||||
|
||||
println("a > b")
|
||||
|
||||
else
|
||||
|
||||
println("a <= b")
|
||||
|
||||
// When Expression
|
||||
|
||||
when {
|
||||
a < b -> {
|
||||
println("a < b")
|
||||
}
|
||||
|
||||
a == b ->
|
||||
|
||||
println("a == b")
|
||||
a > b -> {
|
||||
println("a > b")
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var x = 1
|
||||
when (x) {
|
||||
1 -> print("x == 1")
|
||||
2 -> print("x == 2")
|
||||
else -> { // Note the block
|
||||
print("x is neither 1 nor 2")
|
||||
}
|
||||
}
|
||||
x = 2
|
||||
when (x) {
|
||||
1 -> print("x == 1")
|
||||
2 -> print("x == 2")
|
||||
else -> { // Note the block
|
||||
print("x is neither 1 nor 2")
|
||||
}
|
||||
}
|
||||
x = 3
|
||||
when (x) {
|
||||
1 -> print("x == 1")
|
||||
2 -> print("x == 2")
|
||||
else -> { // Note the block
|
||||
print("x is neither 1 nor 2")
|
||||
}
|
||||
}
|
||||
|
||||
when (x) {
|
||||
0, 1
|
||||
->
|
||||
print("x == 0 or x == 1")
|
||||
else ->
|
||||
print("otherwise")
|
||||
}
|
||||
|
||||
when {
|
||||
else -> println("=)")
|
||||
}
|
||||
|
||||
// While Loops
|
||||
|
||||
do {
|
||||
b++
|
||||
} while (b < 5)
|
||||
|
||||
while (a < 10) {
|
||||
a++
|
||||
if (a > 7) {
|
||||
println(a)
|
||||
}
|
||||
}
|
||||
|
||||
while (a > 0)
|
||||
|
||||
a--
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package coverage.basic.jumps
|
||||
|
||||
fun simpleReturn(n: Int) {
|
||||
if (n == 0) return
|
||||
println(n)
|
||||
}
|
||||
|
||||
fun returnFromIfBranch(n: Int) {
|
||||
if (n > 0) {
|
||||
if (n > 10) {
|
||||
return
|
||||
}
|
||||
} else if (n < -10) {
|
||||
return
|
||||
} else if (n == 0) {
|
||||
return
|
||||
}
|
||||
println(n)
|
||||
}
|
||||
|
||||
fun returnFromWhenBranch(n: Int) {
|
||||
when {
|
||||
n == 0 -> return
|
||||
n == 1 -> return
|
||||
n == 2 -> {
|
||||
println(n)
|
||||
return
|
||||
}
|
||||
else -> println(n)
|
||||
}
|
||||
}
|
||||
|
||||
fun breakFromWhile() {
|
||||
var a = 7
|
||||
while (true) {
|
||||
if (a < 4) break
|
||||
println(a)
|
||||
a--
|
||||
}
|
||||
}
|
||||
|
||||
fun continueFromDoWhile() {
|
||||
var a = 0
|
||||
do {
|
||||
if (a % 3 == 0) {
|
||||
a++
|
||||
continue
|
||||
}
|
||||
println(a)
|
||||
a++
|
||||
} while (a < 10)
|
||||
}
|
||||
|
||||
fun singleReturn() {
|
||||
return
|
||||
}
|
||||
|
||||
fun nestedReturn() {
|
||||
while (true) {
|
||||
while (true) {
|
||||
while (true) {
|
||||
if (1 < 2) {
|
||||
return
|
||||
}
|
||||
println()
|
||||
}
|
||||
println()
|
||||
}
|
||||
}
|
||||
println()
|
||||
}
|
||||
|
||||
fun main() {
|
||||
simpleReturn(0)
|
||||
simpleReturn(1)
|
||||
|
||||
returnFromIfBranch(1)
|
||||
returnFromIfBranch(11)
|
||||
returnFromIfBranch(-11)
|
||||
returnFromIfBranch(0)
|
||||
|
||||
returnFromWhenBranch(0)
|
||||
returnFromWhenBranch(1)
|
||||
returnFromWhenBranch(2)
|
||||
|
||||
breakFromWhile()
|
||||
continueFromDoWhile()
|
||||
singleReturn()
|
||||
nestedReturn()
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package coverage_library
|
||||
|
||||
fun foo() = "foo"
|
||||
@@ -1,5 +0,0 @@
|
||||
package coverage.basic.library
|
||||
|
||||
fun main() {
|
||||
println(coverage_library.foo())
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package coverage.basic.program
|
||||
|
||||
fun main() {
|
||||
println("OK")
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package coverage.basic.smoke0
|
||||
|
||||
data class User(val name: String)
|
||||
|
||||
fun main() {
|
||||
val user = User("Happy Kotlin/Native user")
|
||||
println(user)
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package coverage.basic.smoke1
|
||||
|
||||
class A(val prop: Int) {
|
||||
|
||||
constructor() : this(1)
|
||||
|
||||
fun action1() {
|
||||
println(prop)
|
||||
}
|
||||
|
||||
fun action2() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class B private constructor(val prop: String) {
|
||||
|
||||
init {
|
||||
println("init block")
|
||||
}
|
||||
|
||||
constructor(prop1: String, prop2: String) : this("$prop1, $prop2")
|
||||
|
||||
constructor() : this("dummy") {
|
||||
if (1 > 2) {
|
||||
println("uncovered")
|
||||
} else {
|
||||
println("foo")
|
||||
}
|
||||
println("bar")
|
||||
}
|
||||
|
||||
override fun toString() = prop
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val a1 = A(2)
|
||||
a1.action1()
|
||||
a1.action2()
|
||||
val a2 = A()
|
||||
a2.action1()
|
||||
a2.action1()
|
||||
|
||||
val b1 = B("Hello", "world")
|
||||
val b2 = B()
|
||||
println(b1)
|
||||
println(b2)
|
||||
}
|
||||
@@ -58,7 +58,6 @@ val buildSamplesWithPlatformLibs by tasks.creating {
|
||||
dependsOn(":objc:assemble")
|
||||
dependsOn(":opengl:assemble")
|
||||
dependsOn(":uikit:assemble")
|
||||
dependsOn(":coverage:assemble")
|
||||
dependsOn(":watchos:assemble")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
// Determine host preset.
|
||||
val hostOs = System.getProperty("os.name")
|
||||
val isMingwX64 = hostOs.startsWith("Windows")
|
||||
|
||||
// Create a target for the host platform.
|
||||
val hostTarget = when {
|
||||
hostOs == "Mac OS X" -> macosX64("coverage")
|
||||
hostOs == "Linux" -> linuxX64("coverage")
|
||||
isMingwX64 -> mingwX64("coverage")
|
||||
else -> throw GradleException("Host OS '$hostOs' is not supported in Kotlin/Native $project.")
|
||||
}
|
||||
|
||||
hostTarget.apply {
|
||||
binaries {
|
||||
executable(listOf(DEBUG))
|
||||
}
|
||||
binaries.getTest("DEBUG").apply {
|
||||
freeCompilerArgs += listOf("-Xlibrary-to-cover=${compilations["main"].output.classesDirs.singleFile.absolutePath}")
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
val coverageMain by getting
|
||||
val coverageTest by getting
|
||||
}
|
||||
}
|
||||
|
||||
tasks.create("createCoverageReport") {
|
||||
dependsOn("coverageTest")
|
||||
|
||||
description = "Create coverage report"
|
||||
|
||||
doLast {
|
||||
val testDebugBinary = kotlin.targets["coverage"].let { it as KotlinNativeTarget }.binaries.getTest("DEBUG").outputFile
|
||||
exec {
|
||||
commandLine("llvm-profdata", "merge", "$testDebugBinary.profraw", "-o", "$testDebugBinary.profdata")
|
||||
}
|
||||
exec {
|
||||
commandLine("llvm-cov", "show", "$testDebugBinary", "-instr-profile", "$testDebugBinary.profdata")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
kotlin.code.style=official
|
||||
@@ -1,17 +0,0 @@
|
||||
fun baz(args: Array<String>) {
|
||||
if (args.size > 4) {
|
||||
println("Too many")
|
||||
} else {
|
||||
println("Fine")
|
||||
}
|
||||
}
|
||||
|
||||
class A {
|
||||
init {
|
||||
println("A::init")
|
||||
}
|
||||
|
||||
fun f() {
|
||||
println("A::f")
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fun main() {
|
||||
baz(arrayOf("abc"))
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class CoverageTests {
|
||||
@Test
|
||||
fun testHello() {
|
||||
main()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testA() {
|
||||
val a = A()
|
||||
a.f()
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,6 @@ if (isMacos || isLinux || isWindows) {
|
||||
include(":libcurl")
|
||||
include(":videoplayer")
|
||||
include(":workers")
|
||||
include(":coverage")
|
||||
}
|
||||
|
||||
if (isMacos || isLinux) {
|
||||
|
||||
Reference in New Issue
Block a user