All Downloads are FREE. Search and download functionalities are using the official Maven repository.

d.core.2024.9.2.source-code.Jacoco.kt Maven / Gradle / Ivy

The newest version!
package edu.illinois.cs.cs125.jeed.core

import com.squareup.moshi.JsonClass
import org.jacoco.core.analysis.Analyzer
import org.jacoco.core.analysis.CoverageBuilder
import org.jacoco.core.analysis.IClassCoverage
import org.jacoco.core.analysis.ICounter
import org.jacoco.core.analysis.ILine
import org.jacoco.core.data.ExecutionDataStore
import org.jacoco.core.data.SessionInfoStore
import org.jacoco.core.instr.Instrumenter
import org.jacoco.core.runtime.IRuntime
import org.jacoco.core.runtime.RuntimeData
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes

object Jacoco : SandboxPlugin {
    private val instrumenter = Instrumenter(IsolatedJacocoRuntime)

    override fun createInstrumentationData(
        arguments: Unit,
        classLoaderConfiguration: Sandbox.ClassLoaderConfiguration,
        allPlugins: List>,
    ): Any = JacocoInstrumentationData()

    override fun transformBeforeSandbox(
        bytecode: ByteArray,
        name: String,
        instrumentationData: Any?,
        context: RewritingContext,
    ): ByteArray {
        if (context != RewritingContext.UNTRUSTED) return bytecode
        instrumentationData as JacocoInstrumentationData
        instrumentationData.coverageClasses[name] = bytecode
        return instrumenter.instrument(bytecode, name)
    }

    override val requiredClasses: Set>
        get() = setOf(IsolatedJacocoRuntime.RuntimeDataAccessor::class.java)

    override fun createInitialData(instrumentationData: Any?, executionArguments: Sandbox.ExecutionArguments): Any = JacocoWorkingData(instrumentationData as JacocoInstrumentationData)

    override fun createFinalData(workingData: Any?): CoverageBuilder {
        workingData as JacocoWorkingData
        val executionData = ExecutionDataStore()
        workingData.runtimeData.collect(executionData, SessionInfoStore(), false)
        val coverageBuilder = CoverageBuilder()
        Analyzer(executionData, coverageBuilder).apply {
            try {
                for ((name, bytes) in workingData.instrumentationData.coverageClasses) {
                    analyzeClass(bytes, name)
                }
            } catch (_: Exception) {
            }
        }
        return coverageBuilder
    }
}

private class JacocoInstrumentationData(
    val coverageClasses: MutableMap = mutableMapOf(),
)

private class JacocoWorkingData(
    val instrumentationData: JacocoInstrumentationData,
    val runtimeData: RuntimeData = RuntimeData(),
)

object IsolatedJacocoRuntime : IRuntime {
    private const val STACK_SIZE = 6

    override fun generateDataAccessor(classid: Long, classname: String?, probecount: Int, mv: MethodVisitor): Int {
        @Suppress("SpellCheckingInspection")
        mv.visitMethodInsn(
            Opcodes.INVOKESTATIC,
            classNameToPath(RuntimeDataAccessor::class.java.name),
            "get",
            "()Ljava/lang/Object;",
            false,
        )
        RuntimeData.generateAccessCall(classid, classname, probecount, mv)
        return STACK_SIZE
    }

    override fun startup(data: RuntimeData?) {
        // Nothing to do - the data is owned by the sandbox task
    }

    override fun shutdown() {
        // Nothing to do - the data is owned by the sandbox task
    }

    object RuntimeDataAccessor {
        @JvmStatic
        fun get(): Any {
            val workingData: JacocoWorkingData = Sandbox.CurrentTask.getWorkingData(Jacoco)
            return workingData.runtimeData
        }
    }
}

enum class LineCoverage(@Suppress("unused") val description: String) {
    EMPTY("empty"),
    NOT_COVERED("not covered"),
    PARTLY_COVERED("partly covered"),
    COVERED("fully covered"),
    IGNORED("ignored"),
}

private fun ILine.toLineCoverage() = when (status) {
    ICounter.EMPTY -> LineCoverage.EMPTY
    ICounter.NOT_COVERED -> LineCoverage.NOT_COVERED
    ICounter.PARTLY_COVERED -> LineCoverage.PARTLY_COVERED
    ICounter.FULLY_COVERED -> LineCoverage.COVERED
    else -> error("Invalid iLine status: $status")
}

typealias FileCoverage = Map
typealias ClassCoverage = Map

@JsonClass(generateAdapter = true)
data class CoverageResult(
    val byFile: Map,
    val byClass: Map,
)

fun Source.processCoverage(
    coverage: CoverageBuilder,
): CoverageResult {
    val byFile = coverage.sourceFiles.filter {
        this.sources.keys.contains(it.name)
    }.associate { fileCoverage ->
        fileCoverage.name!! to (fileCoverage.firstLine..fileCoverage.lastLine).toList().map { lineNumber ->
            lineNumber to fileCoverage.getLine(lineNumber).toLineCoverage()
        }.mapNotNull { (lineNumber, coverage) ->
            try {
                Pair(mapLocation(fileCoverage.name!!, Location(lineNumber, 0)).line, coverage)
            } catch (_: Exception) {
                null
            }
        }.toMap()
    }
    val byClass = coverage.classes.associate { classCoverage ->
        classCoverage.name!! to (classCoverage.firstLine..classCoverage.lastLine).toList().map { lineNumber ->
            lineNumber to classCoverage.getLine(lineNumber).toLineCoverage()
        }.mapNotNull { (lineNumber, coverage) ->
            if (this is Snippet || this is TemplatedSource) {
                val filename = if (this is Snippet) {
                    SNIPPET_SOURCE
                } else {
                    this.sources.keys.also {
                        check(it.size == 1) { "No support for multi-source templates yet" }
                    }.first()
                }
                try {
                    Pair(mapLocation(filename, Location(lineNumber, 0)).line, coverage)
                } catch (_: Exception) {
                    null
                }
            } else {
                Pair(lineNumber, coverage)
            }
        }.toMap()
    }
    return CoverageResult(byFile, byClass)
}

fun IClassCoverage.allMissedLines() = (firstLine..lastLine).toList().filter {
    getLine(it).status == ICounter.NOT_COVERED || getLine(it).status == ICounter.PARTLY_COVERED
}

@Suppress("unused")
fun IClassCoverage.printLines() = (firstLine..lastLine).toList().forEach {
    println("$it: ${getLine(it).print()}")
}

private fun ILine.print() = when (status) {
    ICounter.EMPTY -> "empty"
    ICounter.NOT_COVERED -> "uncovered"
    ICounter.PARTLY_COVERED -> "partial"
    ICounter.FULLY_COVERED -> "fully"
    else -> error("Invalid line status: $status")
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy