[evaluation-plugin] LME-56 Encapsulate everything project-specific to EvaluationEnvironment and create an example of standalone environment

- introduce DatasetContext for better control over data sources
- extract actions and associated fields like language to an optional config
- create sub-config for standalone datasets
- adapt result report rendering more convenient for csv dataset
- make language configuration optional where possible
- make application lifecycle management more transparent and add sleep for quick runs (intellij can throw errors)
- fix inability to use colons in chunk name


Merge-request: IJ-MR-143249
Merged-by: Roman Vasiliev <Roman.Vasiliev@jetbrains.com>

GitOrigin-RevId: 0b15518f22af27ee9cfb544e99010ec293f31eb5
This commit is contained in:
Roman Vasiliev
2024-09-13 18:48:52 +00:00
committed by intellij-monorepo-bot
parent d17f70059a
commit b177803d6f
55 changed files with 1211 additions and 445 deletions

View File

@@ -10,7 +10,7 @@ object EvaluationFilterReader {
for ((id, description) in map) {
val configuration = EvaluationFilterManager.getConfigurationById(id)
?: throw IllegalStateException("Unknown filter: $id")
assert(configuration.isLanguageSupported(language)) { "filter $id is not supported for this language" }
assert(configuration.isLanguageSupported(language)) { "filter $id is not supported for this language: ${language}" }
evaluationFilters[id] = configuration.buildFromJson(description)
}
return evaluationFilters

View File

@@ -40,7 +40,7 @@ class FusLogsSaver(private val finalStorageDir: Path, private val allowedGroups:
}
@OptIn(ExperimentalPathApi::class)
override fun save(languageName: String, trainingPercentage: Int) {
override fun save(languageName: String?, trainingPercentage: Int) {
try {
for (sessionTemporaryRelativePath in temporaryFusLogsDirectory.walk()) {
val sessionTemporaryPath = temporaryFusLogsDirectory / sessionTemporaryRelativePath
@@ -54,8 +54,8 @@ class FusLogsSaver(private val finalStorageDir: Path, private val allowedGroups:
}
}
private fun obtainFinalLogsDirectory(languageName: String): Path {
val fusFinalDirectory = finalStorageDir / languageName
private fun obtainFinalLogsDirectory(languageName: String?): Path {
val fusFinalDirectory = if (languageName != null) finalStorageDir / languageName else finalStorageDir
if (!fusFinalDirectory.exists()) {
fusFinalDirectory.createDirectory()
}

View File

@@ -5,24 +5,20 @@ import com.intellij.cce.actions.*
import com.intellij.cce.core.Session
import com.intellij.cce.util.FileTextUtil.computeChecksum
import com.intellij.cce.util.FileTextUtil.getDiff
import java.nio.file.Paths
import kotlin.random.Random
class ActionInvokingInterpreter(private val invokersFactory: InvokersFactory,
private val handler: InterpretationHandler,
private val filter: InterpretFilter,
private val order: InterpretationOrder,
private val projectPath: String?) : Interpreter {
private val order: InterpretationOrder) {
override fun interpret(fileActions: FileActions, sessionHandler: (Session) -> Unit): List<Session> {
fun interpret(fileActions: FileActions, sessionHandler: (Session) -> Unit): List<Session> {
val actionsInvoker = invokersFactory.createActionsInvoker()
val featureInvoker = invokersFactory.createFeatureInvoker()
val sessions = mutableListOf<Session>()
val filePath = if (projectPath == null) fileActions.path else Paths.get(projectPath).resolve(fileActions.path).toString()
val needToClose = !actionsInvoker.isOpen(filePath)
val text = actionsInvoker.openFile(filePath)
val needToClose = !actionsInvoker.isOpen(fileActions.path)
val text = actionsInvoker.openFile(fileActions.path)
if (fileActions.checksum != computeChecksum(text)) {
handler.onErrorOccurred(IllegalStateException("File $filePath has been modified."), fileActions.sessionsCount)
handler.onErrorOccurred(IllegalStateException("File ${fileActions.path} has been modified."), fileActions.sessionsCount)
return emptyList()
}
var shouldCompleteToken = filter.shouldCompleteToken()
@@ -57,22 +53,11 @@ class ActionInvokingInterpreter(private val invokersFactory: InvokersFactory,
if (text != resultText) {
actionsInvoker.deleteRange(0, resultText.length)
actionsInvoker.printText(text)
if (needToClose) actionsInvoker.closeFile(filePath)
if (needToClose) actionsInvoker.closeFile(fileActions.path)
throw IllegalStateException("Text before and after interpretation doesn't match. Diff:\n${getDiff(text, resultText)}")
}
if (needToClose) actionsInvoker.closeFile(filePath)
if (needToClose) actionsInvoker.closeFile(fileActions.path)
handler.onFileProcessed(fileActions.path)
return sessions.sortedBy { it.offset }
}
private fun List<Action>.reorder(order: InterpretationOrder): List<Action> {
val groups = groupBy { it.sessionId }.values
return when (order) {
InterpretationOrder.LINEAR -> groups.flatten()
InterpretationOrder.REVERSED -> groups.reversed().flatten()
InterpretationOrder.RANDOM -> groups.shuffled(ORDER_RANDOM).flatten()
}
}
}
private val ORDER_RANDOM = Random(42)

View File

@@ -1,8 +1,23 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.cce.interpreter
import com.intellij.cce.actions.Action
import com.intellij.cce.interpreter.InterpretationOrder.*
import kotlin.random.Random
enum class InterpretationOrder {
LINEAR,
REVERSED,
RANDOM
}
private val ORDER_RANDOM = Random(42)
fun <T : Action> List<T>.reorder(order: InterpretationOrder): List<T> {
val groups = groupBy { it.sessionId }.values
return when (order) {
LINEAR -> groups.flatten()
REVERSED -> groups.reversed().flatten()
RANDOM -> groups.shuffled(ORDER_RANDOM).flatten()
}
}

View File

@@ -1,9 +0,0 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.cce.interpreter
import com.intellij.cce.actions.*
import com.intellij.cce.core.Session
interface Interpreter {
fun interpret(fileActions: FileActions, sessionHandler: (Session) -> Unit): List<Session>
}

View File

@@ -1,15 +0,0 @@
package com.intellij.cce.interpreter
import com.intellij.cce.actions.FileActions
import com.intellij.cce.core.Session
import com.intellij.cce.workspace.storages.LogsSaver
class LoggingInterpreterWrapper(private val baseInterpreter: Interpreter, private val logsSaver: LogsSaver) : Interpreter {
override fun interpret(fileActions: FileActions, sessionHandler: (Session) -> Unit): List<Session> {
return logsSaver.invokeRememberingLogs {
baseInterpreter.interpret(fileActions, sessionHandler)
}
}
}
fun Interpreter.wrapLogging(logsSaver: LogsSaver): Interpreter = LoggingInterpreterWrapper(this, logsSaver)

View File

@@ -35,7 +35,7 @@ abstract class BaseCompletionGolfFileReportGenerator(
}
}
override fun getHtml(fileEvaluations: List<FileEvaluationInfo>, fileName: String, resourcePath: String, text: String): String {
override fun getHtml(fileEvaluations: List<FileEvaluationInfo>, resourcePath: String, text: String): String {
return createHTML().body {
div("cg") {
div {

View File

@@ -15,7 +15,7 @@ open class BasicFileReportGenerator(
dirs: GeneratorDirectories
) : FileReportGenerator(featuresStorages, dirs, filterName, comparisonFilterName) {
override fun getHtml(fileEvaluations: List<FileEvaluationInfo>, fileName: String, resourcePath: String, text: String): String {
override fun getHtml(fileEvaluations: List<FileEvaluationInfo>, resourcePath: String, text: String): String {
val sessions = fileEvaluations.map { it.sessionsInfo.sessions }
val maxLookupOrder = sessions.flatMap { session -> session.map { it.lookups.size - 1 } }.maxOrNull() ?: 0
return createHTML().body {

View File

@@ -25,23 +25,23 @@ abstract class FileReportGenerator(
val reportReferences: MutableMap<String, ReferenceInfo> = mutableMapOf()
abstract fun getHtml(fileEvaluations: List<FileEvaluationInfo>, fileName: String, resourcePath: String, text: String): String
abstract fun getHtml(fileEvaluations: List<FileEvaluationInfo>, resourcePath: String, text: String): String
abstract val scripts: List<Resource>
override fun generateFileReport(sessions: List<FileEvaluationInfo>) {
val fileInfo = sessions.first()
val fileName = if (sessions.size > 1) {
"${fileInfo.sessionsInfo.projectName} - ${File(fileInfo.sessionsInfo.filePath).name}"
} else {
File(fileInfo.sessionsInfo.filePath).name
}
val (resourcePath, reportPath) = dirs.getPaths(fileName)
val fileName = File(fileInfo.sessionsInfo.filePath).name
val fileNameAlreadyHasProject = fileName.startsWith(fileInfo.sessionsInfo.projectName)
val internalFileName =
if (sessions.size > 1 && !fileNameAlreadyHasProject) "${fileInfo.sessionsInfo.projectName} - $fileName" else fileName
val (resourcePath, reportPath) = dirs.getPaths(internalFileName)
val sessionsJson = sessionSerializer.serialize(sessions.map { it.sessionsInfo.sessions }.flatten())
val resourceFile = File(resourcePath.toString())
resourceFile.writeText("var sessions = {};\nvar features={};\nvar fullLineLog=[];\nsessions = ${parseJsonInJs(sessionsJson)};\n")
processStorages(sessions, resourceFile)
val reportTitle = "Evaluation Report for file $fileName (project: ${fileInfo.sessionsInfo.projectName})"
val titleProject = if (fileNameAlreadyHasProject) "" else " (project: ${fileInfo.sessionsInfo.projectName})"
val reportTitle = "Evaluation Report for $fileName$titleProject"
createHTML().html {
head {
createHead(this, reportTitle, resourcePath)
@@ -51,15 +51,15 @@ abstract class FileReportGenerator(
unsafe {
+getHtml(
sessions.sortedBy { it.evaluationType },
fileName,
dirs.filesDir.relativize(resourcePath).toString(),
fileInfo.sessionsInfo.text
)
}
}
}.also { html -> FileWriter(reportPath.toString()).use { it.write(html) } }
val fileReference = "$fileName (${fileInfo.sessionsInfo.filePath})"
reportReferences[fileReference] = ReferenceInfo(reportPath, sessions.map { it.metrics }.flatten())
val fullPathDetails = if (fileInfo.sessionsInfo.filePath != fileName) " (${fileInfo.sessionsInfo.filePath})" else ""
val tableReference = "$fileName$fullPathDetails"
reportReferences[tableReference] = ReferenceInfo(reportPath, sessions.map { it.metrics }.flatten())
}
open fun createHead(head: HEAD, reportTitle: String, resourcePath: Path) = with(head) {

View File

@@ -110,7 +110,7 @@ class HtmlReportGenerator(
}
body {
h1 { +reportTitle }
h3 { +"${fileGenerator.reportReferences.size} file(s) successfully processed" }
h3 { +"${fileGenerator.reportReferences.size} chunk(s) successfully processed" }
h3 { +"${errorReferences.size} errors occurred" }
unsafe { raw(getToolbar(globalMetrics)) }
div { id = "metricsTable" }

View File

@@ -8,36 +8,31 @@ import com.intellij.cce.workspace.filter.CompareSessionsFilter
import com.intellij.cce.workspace.filter.NamedFilter
import com.intellij.cce.workspace.filter.SessionsFilter
import java.nio.file.Paths
import kotlin.io.path.absolute
/**
* Represents a configuration of the evaluation process.
*
* @property projectPath The path to the project that will be used for the evaluation.
* @property projectName The name of the project. It may differ from the root directory name.
* @property language The programming language whose files are used in the evaluation.
* @property outputDir The output directory for the evaluation results.
* @property strategy The evaluation strategy used.
* @property actions The configuration for actions generation step.
* @property fileDataset Dataset configuration for standalone setups
* @property interpret The configuration for actions interpretation step.
* @property reorder The configuration for element reordering step.
* @property reports The configuration for report generation step.
*/
data class Config private constructor(
val projectPath: String,
val projectName: String,
val language: String,
val outputDir: String,
val strategy: EvaluationStrategy,
val actions: ActionsGeneration,
val actions: ActionsGeneration?,
val fileDataset: FileDataset?,
val interpret: ActionsInterpretation,
val reorder: ReorderElements,
val reports: ReportGeneration
) {
companion object {
fun build(projectPath: String, language: String, init: Builder.() -> Unit): Config {
val builder = Builder(projectPath, language)
fun build(init: Builder.() -> Unit): Config {
val builder = Builder()
builder.init()
return builder.build()
}
@@ -49,17 +44,37 @@ data class Config private constructor(
}
}
@Suppress("UNCHECKED_CAST")
fun <T : EvaluationStrategy> strategy(): T = strategy as T // TODO add type parameter to Config?
/**
* Represents the configuration for generating actions.
*
* @property projectPath The path to the project that will be used for the evaluation.
* @property projectName The name of the project. It may differ from the root directory name.
* @property language The programming language whose files are used in the evaluation.
* @property evaluationRoots The list of evaluation roots. Directories and files with relative and absolute paths are allowed.
* @property ignoreFileNames The set of file names to ignore. Files and directories with these names inside [evaluationRoots] will be skipped.
*/
data class ActionsGeneration internal constructor(
val projectPath: String,
val projectName: String,
val language: String,
val evaluationRoots: List<String>,
val ignoreFileNames: Set<String>,
)
/**
* Represents a configuration for datasets stored on a file system (probably in one file).
*
* @property url The URL of the file. Check DatasetRef for available options.
* @property chunkSize The size of each chunk when reading and rendering the file.
*/
data class FileDataset internal constructor(
val url: String,
val chunkSize: Int,
)
/**
* Represents the configuration for the interpretation of actions.
*
@@ -116,11 +131,11 @@ data class Config private constructor(
val sessionsFilters: List<SessionsFilter>,
val comparisonFilters: List<CompareSessionsFilter>)
class Builder internal constructor(private val projectPath: String, private val language: String) {
var evaluationRoots = mutableListOf<String>()
var ignoreFileNames = mutableSetOf<String>()
var projectName = projectPath.split('/').last()
var outputDir: String = Paths.get(projectPath, "completion-evaluation").toAbsolutePath().toString()
class Builder internal constructor() {
var actions: ActionsGeneration? = null
var fileDataset: FileDataset? = null
var outputDir: String? = null
var strategy: EvaluationStrategy = EvaluationStrategy.defaultStrategy
var saveLogs = false
var saveFusLogs = false
@@ -143,11 +158,11 @@ data class Config private constructor(
private val sessionsFilters: MutableList<SessionsFilter> = mutableListOf()
private val comparisonFilters: MutableList<CompareSessionsFilter> = mutableListOf()
constructor(config: Config) : this(config.projectPath, config.language) {
constructor(config: Config) : this() {
actions = config.actions
fileDataset = config.fileDataset
outputDir = config.outputDir
strategy = config.strategy
evaluationRoots.addAll(config.actions.evaluationRoots)
ignoreFileNames.addAll(config.actions.ignoreFileNames)
saveLogs = config.interpret.saveLogs
saveFusLogs = config.interpret.saveFusLogs
saveFeatures = config.interpret.saveFeatures
@@ -180,16 +195,16 @@ data class Config private constructor(
}
}
private fun buildOutputDir() =
outputDir
?: actions?.projectPath?.let { Paths.get(it, "completion-evaluation").toAbsolutePath().toString() }
?: throw IllegalStateException("Output directory is not defined")
fun build(): Config = Config(
Paths.get(projectPath).absolute().toString(),
projectName,
language,
outputDir,
buildOutputDir(),
strategy,
ActionsGeneration(
evaluationRoots,
ignoreFileNames,
),
actions,
fileDataset,
ActionsInterpretation(
experimentGroup,
sessionsLimit,

View File

@@ -17,27 +17,22 @@ import java.nio.file.Path
object ConfigFactory {
const val DEFAULT_CONFIG_NAME = "config.json"
private const val NO_LANGUAGE = "NO LANGUAGE PROVIDED"
private lateinit var gson: Gson
private fun defaultConfig(projectPath: String = "", language: String = "Java"): Config =
Config.build(projectPath, language) {}
private fun defaultConfig(): Config =
Config.build {}
fun <T : EvaluationStrategy> load(path: Path, strategySerializer: StrategySerializer<T>): Config {
gson = GsonBuilder()
.serializeNulls()
.setPrettyPrinting()
.registerTypeAdapter(SessionsFilter::class.java,
SessionFiltersSerializer())
.registerTypeAdapter(EvaluationStrategy::class.java, strategySerializer)
.create()
gson = createGson(strategySerializer)
val configFile = path.toFile()
if (!configFile.exists()) {
save(defaultConfig(), path.parent, configFile.name)
throw IllegalArgumentException("Config file missing. Config created by path: ${configFile.absolutePath}. Fill settings in config.")
}
return deserialize(configFile.readText(), strategySerializer)
return deserialize(gson, configFile.readText(), strategySerializer)
}
fun save(config: Config, directory: Path, name: String = DEFAULT_CONFIG_NAME) {
@@ -47,28 +42,74 @@ object ConfigFactory {
fun serialize(config: Config): String = gson.toJson(config)
fun <T : EvaluationStrategy> deserialize(json: String, strategySerializer: StrategySerializer<T>): Config {
fun <T : EvaluationStrategy> deserialize(gson: Gson, json: String, strategySerializer: StrategySerializer<T>): Config {
val map = gson.fromJson(json, HashMap<String, Any>().javaClass)
val languageName = map.getAs<String>("language")
return Config.build(map.handleEnv("projectPath"), languageName) {
return Config.build {
outputDir = map.handleEnv("outputDir")
if (map.containsKey("projectName")) {
projectName = map.handleEnv("projectName")
}
deserializeStrategy(map.getIfExists("strategy"), strategySerializer, languageName, this)
deserializeActionsGeneration(map.getIfExists("actions"), languageName, this)
deserializeActionsGeneration(
map.getIfExists("actions"),
map.getIfExists<String>("projectPath")?.handleEnv(),
map.getIfExists<String>("projectName")?.handleEnv(),
map.getIfExists<String>("language"),
this
)
deserializeFileDataset(map.getIfExists("fileDataset"), this)
deserializeStrategy(map.getIfExists("strategy"), strategySerializer, actions?.language, this)
deserializeActionsInterpretation(map.getIfExists("interpret"), this)
deserializeReorderElements(map.getIfExists("reorder"), this)
deserializeReportGeneration(map.getIfExists("reports"), languageName, this)
deserializeReportGeneration(map.getIfExists("reports"), actions?.language, this)
}
}
private fun deserializeActionsGeneration(map: Map<String, Any>?, language: String, builder: Config.Builder) {
if (map == null) return
builder.evaluationRoots = map.getAs("evaluationRoots")
if (map.containsKey("ignoreFileNames")) {
builder.ignoreFileNames = map.getAs<List<String>>("ignoreFileNames").toMutableSet()
fun <T : EvaluationStrategy> createGson(strategySerializer: StrategySerializer<T>): Gson {
return GsonBuilder()
.serializeNulls()
.setPrettyPrinting()
.registerTypeAdapter(SessionsFilter::class.java,
SessionFiltersSerializer())
.registerTypeAdapter(EvaluationStrategy::class.java, strategySerializer)
.create()
}
private fun deserializeActionsGeneration(
map: Map<String, Any>?,
projectPath: String?,
projectName: String?,
language: String?,
builder: Config.Builder
) {
if (map == null && projectPath == null && language == null && projectName == null) {
return
}
if (map != null) {
val resultProjectPath = projectPath ?: map.handleEnv("projectPath")
builder.actions = Config.ActionsGeneration(
resultProjectPath,
projectName ?: map.getIfExists<String>("projectName")?.handleEnv() ?: resultProjectPath.split('/').last(),
language ?: map.getAs("language"),
map.getAs("evaluationRoots"),
map.getIfExists<List<String>>("ignoreFileNames")?.toSet() ?: emptySet()
)
return
}
throw IllegalStateException("Missing 'actions' in config when 'language', 'projectPath' or 'projectName' was provided")
}
private fun deserializeFileDataset(
map: Map<String, Any>?,
builder: Config.Builder
) {
if (map == null) {
return
}
builder.fileDataset = Config.FileDataset(
map.getAs("url"),
map.getAs<Double>("chunkSize").toInt(),
)
}
private fun deserializeActionsInterpretation(map: Map<String, Any>?, builder: Config.Builder) {
@@ -103,11 +144,11 @@ object ConfigFactory {
private fun <T : EvaluationStrategy> deserializeStrategy(map: Map<String, Any>?,
strategySerializer: StrategySerializer<T>,
language: String,
language: String?,
builder: Config.Builder) {
if (map == null)
throw IllegalArgumentException("No strategy found in config!")
builder.strategy = strategySerializer.deserialize(map, language)
builder.strategy = strategySerializer.deserialize(map, language ?: NO_LANGUAGE)
}
private fun deserializeReorderElements(map: Map<String, Any>?, builder: Config.Builder) {
@@ -122,7 +163,7 @@ object ConfigFactory {
}
}
private fun deserializeReportGeneration(map: Map<String, Any>?, language: String, builder: Config.Builder) {
private fun deserializeReportGeneration(map: Map<String, Any>?, language: String?, builder: Config.Builder) {
if (map == null) return
builder.evaluationTitle = map.handleEnv("evaluationTitle")
if (map.containsKey("defaultMetrics")) {
@@ -132,7 +173,7 @@ object ConfigFactory {
val filters = mutableListOf<SessionsFilter>()
filtersList.forEach {
val name = it.getAs<String>("name")
filters.add(SessionsFilter(name, EvaluationFilterReader.readFilters(it, language)))
filters.add(SessionsFilter(name, EvaluationFilterReader.readFilters(it, language ?: NO_LANGUAGE)))
}
builder.mergeFilters(filters)
val comparisonFiltersList = map.getAs<List<Map<String, Any>>>("comparisonFilters")
@@ -144,6 +185,7 @@ object ConfigFactory {
}
private fun Map<String, *>.handleEnv(key: String): String = StrSubstitutor.replaceSystemProperties(getAs(key))
private fun String.handleEnv(): String = StrSubstitutor.replaceSystemProperties(this)
private class SessionFiltersSerializer : JsonSerializer<SessionsFilter> {
override fun serialize(src: SessionsFilter, typeOfSrc: Type, context: JsonSerializationContext): JsonObject {

View File

@@ -6,9 +6,8 @@ import com.intellij.cce.evaluable.EvaluationStrategy
import com.intellij.cce.evaluable.StrategySerializer
import com.intellij.cce.fus.FusLogsSaver
import com.intellij.cce.workspace.storages.*
import com.intellij.cce.workspace.storages.storage.ActionsStorage
import com.intellij.cce.workspace.storages.storage.ActionsStorageFactory
import com.intellij.cce.workspace.storages.storage.getActionsStorageTypeFromEnv
import com.intellij.openapi.util.io.FileUtil
import com.intellij.util.io.createDirectories
import java.io.FileWriter
import java.nio.file.Files
import java.nio.file.Path
@@ -27,8 +26,13 @@ class EvaluationWorkspace private constructor(private val basePath: Path,
return EvaluationWorkspace(Paths.get(workspaceDir).toAbsolutePath(), statsLogsPath)
}
fun create(config: Config, statsLogsPath: Path): EvaluationWorkspace {
val workspace = EvaluationWorkspace(Paths.get(config.outputDir).toAbsolutePath().resolve(formatter.format(Date())), statsLogsPath)
fun create(config: Config, statsLogsPath: Path, debug: Boolean = false): EvaluationWorkspace {
val path = Paths.get(config.outputDir).toAbsolutePath()
.resolve(if (debug) "debug" else formatter.format(Date()))
if (debug) {
FileUtil.deleteRecursively(path)
}
val workspace = EvaluationWorkspace(path, statsLogsPath)
workspace.writeConfig(config)
return workspace
}
@@ -37,7 +41,6 @@ class EvaluationWorkspace private constructor(private val basePath: Path,
private val sessionsDir = subdir("data")
private val fullLineLogsDir = subdir("full-line-logs")
private val featuresDir = subdir("features")
private val actionsDir = subdir("actions")
private val errorsDir = subdir("errors")
private val reportsDir = subdir("reports")
private val pathToConfig = path().resolve(ConfigFactory.DEFAULT_CONFIG_NAME)
@@ -45,8 +48,6 @@ class EvaluationWorkspace private constructor(private val basePath: Path,
val sessionsStorage: CompositeSessionsStorage = CompositeSessionsStorage(sessionsDir.toString())
val actionsStorage: ActionsStorage = ActionsStorageFactory.create(actionsDir.toString(), getActionsStorageTypeFromEnv())
val errorsStorage: FileErrorsStorage = FileErrorsStorage(errorsDir.toString())
val statLogsSaver: StatLogsSaver = StatLogsSaver(statsLogsPath, subdir("logs"))

View File

@@ -34,7 +34,7 @@ class CompositeSessionsStorage(storageDir: String) : SessionsStorage(storageDir)
}
override fun getSessions(path: String): FileSessionsInfo {
val (projectName, filePath) = path.split(":")
val (projectName, filePath) = path.split(":", limit = 2)
return storages[projectName]!!.getSessions(filePath)
}
}

View File

@@ -3,13 +3,13 @@ package com.intellij.cce.workspace.storages
interface LogsSaver {
fun <T> invokeRememberingLogs(action: () -> T): T
fun save(languageName: String, trainingPercentage: Int)
fun save(languageName: String?, trainingPercentage: Int)
}
class NoLogsSaver : LogsSaver {
override fun <T> invokeRememberingLogs(action: () -> T): T = action()
override fun save(languageName: String, trainingPercentage: Int) = Unit
override fun save(languageName: String?, trainingPercentage: Int) = Unit
}
fun logsSaverIf(condition: Boolean, createSaver: () -> LogsSaver): LogsSaver = if (condition) createSaver() else NoLogsSaver()
@@ -23,7 +23,7 @@ private fun makeNestingCollector(saverA: LogsSaver,
}
}
override fun save(languageName: String, trainingPercentage: Int) {
override fun save(languageName: String?, trainingPercentage: Int) {
try {
saverA.save(languageName, trainingPercentage)
}

View File

@@ -17,11 +17,11 @@ class StatLogsSaver(private val logsTemporaryStoragePath: Path, private val fina
override fun <T> invokeRememberingLogs(action: () -> T): T = action()
override fun save(languageName: String, trainingPercentage: Int) {
override fun save(languageName: String?, trainingPercentage: Int) {
val logsDir = logsTemporaryStoragePath.toFile()
if (!logsDir.exists()) return
require(logsDir.isDirectory)
val outputDir = finalStorageDir / languageName
val outputDir = if (languageName != null) finalStorageDir / languageName else finalStorageDir
Files.createDirectories(outputDir)
FileWriter(Paths.get(outputDir.toString(), "full.log").toString()).use { writer ->
for (logChunk in (logsDir.listFiles() ?: emptyArray())

View File

@@ -0,0 +1,24 @@
{
"outputDir": "ml-eval-standalone-example-output",
"strategy": {},
"fileDataset": {
"url": "./latin_letters.csv",
"chunkSize": 10
},
"interpret": {
"saveLogs": false,
"saveFeatures": false,
"sessionProbability": 1.0,
"sessionSeed": null,
"sessionsLimit": 30,
"filesLimit": 2,
"order": "LINEAR",
"trainTestSplit": 70
},
"reports": {
"evaluationTitle": "Default",
"defaultMetrics": null,
"sessionsFilters": [],
"comparisonFilters": []
}
}

View File

@@ -0,0 +1,27 @@
Letter,Type
A,Vowel
B,Consonant
C,Consonant
D,Consonant
E,Vowel
F,Consonant
G,Consonant
H,Consonant
I,Vowel
J,Consonant
K,Consonant
L,Consonant
M,Consonant
N,Consonant
O,Vowel
P,Consonant
Q,Consonant
R,Consonant
S,Consonant
T,Consonant
U,Vowel
V,Consonant
W,Consonant
X,Consonant
Y,Consonant
Z,Consonant
1 Letter Type
2 A Vowel
3 B Consonant
4 C Consonant
5 D Consonant
6 E Vowel
7 F Consonant
8 G Consonant
9 H Consonant
10 I Vowel
11 J Consonant
12 K Consonant
13 L Consonant
14 M Consonant
15 N Consonant
16 O Vowel
17 P Consonant
18 Q Consonant
19 R Consonant
20 S Consonant
21 T Consonant
22 U Vowel
23 V Consonant
24 W Consonant
25 X Consonant
26 Y Consonant
27 Z Consonant

View File

@@ -6,6 +6,8 @@ import com.intellij.cce.workspace.EvaluationWorkspace
interface EvaluationStep {
val name: String
val description: String
}
interface ForegroundEvaluationStep : EvaluationStep {
fun start(workspace: EvaluationWorkspace): EvaluationWorkspace?
}

View File

@@ -5,4 +5,6 @@ import com.intellij.cce.workspace.EvaluationWorkspace
interface FinishEvaluationStep {
fun start(workspace: EvaluationWorkspace, withErrors: Boolean)
class EvaluationCompletedWithErrorsException : Exception("Evaluation completed with errors.")
}

View File

@@ -5,7 +5,7 @@ import com.intellij.cce.core.Language
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project
abstract class SetupSdkStep : EvaluationStep {
abstract class SetupSdkStep : ForegroundEvaluationStep {
companion object {
private val EP_NAME = ExtensionPointName.create<SetupSdkStep>("com.intellij.cce.setupSdkStep")

View File

@@ -1,8 +1,8 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.cce.evaluation
interface UndoableEvaluationStep : EvaluationStep {
interface UndoableEvaluationStep : ForegroundEvaluationStep {
fun undoStep(): UndoStep
interface UndoStep : EvaluationStep
interface UndoStep : ForegroundEvaluationStep
}

View File

@@ -47,6 +47,7 @@
<evaluableFeature implementation="com.intellij.cce.evaluable.testGeneration.TestGenerationFeature"/>
<evaluableFeature implementation="com.intellij.cce.evaluable.completion.TokenCompletionFeature"/>
<evaluableFeature implementation="com.intellij.cce.actions.ContextCollectionFeature"/>
<evaluableFeature implementation="com.intellij.cce.evaluable.standaloneExample.StandaloneExampleFeature"/>
<suggestionsProvider implementation="com.intellij.cce.evaluable.completion.DefaultCompletionProvider"/>
</extensions>

View File

@@ -13,12 +13,15 @@ fun <T : EvaluationStrategy> List<EvaluationWorkspace>.buildMultipleEvaluationsC
): Config {
val existingConfig = this.first().readConfig(strategySerializer)
val projectPath = createTempProject()
return Config.build(projectPath, existingConfig.language) {
return Config.build {
for (workspace in this@buildMultipleEvaluationsConfig) {
val config = workspace.readConfig(strategySerializer)
mergeFilters(config.reports.sessionsFilters)
mergeComparisonFilters(config.reports.comparisonFilters)
}
actions = existingConfig.actions?.copy(
projectPath = projectPath
)
strategy = existingConfig.strategy
outputDir = existingConfig.outputDir
title?.let { evaluationTitle = title }

View File

@@ -14,17 +14,16 @@ import com.intellij.cce.evaluable.EvaluationStrategy
import com.intellij.cce.evaluable.StrategySerializer
import com.intellij.cce.evaluation.BackgroundStepFactory
import com.intellij.cce.evaluation.EvaluationProcess
import com.intellij.cce.evaluation.EvaluationRootInfo
import com.intellij.cce.evaluation.FinishEvaluationStep
import com.intellij.cce.evaluation.step.SetupStatsCollectorStep
import com.intellij.cce.evaluation.step.runInIntellij
import com.intellij.cce.util.ExceptionsUtil.stackTraceToString
import com.intellij.cce.workspace.ConfigFactory
import com.intellij.cce.workspace.EvaluationWorkspace
import com.intellij.ide.impl.runUnderModalProgressIfIsEdt
import com.intellij.openapi.application.ApplicationStarter
import com.intellij.openapi.project.Project
import com.intellij.openapi.application.ex.ApplicationEx.FORCE_EXIT
import com.intellij.openapi.application.ex.ApplicationManagerEx
import com.intellij.platform.ide.bootstrap.commandNameFromExtension
import com.intellij.warmup.util.importOrOpenProjectAsync
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.exists
@@ -36,7 +35,8 @@ internal class CompletionEvaluationStarter : ApplicationStarter {
get() = ApplicationStarter.NOT_IN_EDT
override fun main(args: List<String>) {
MainEvaluationCommand()
fun run() = MainEvaluationCommand()
.subcommands(
FullCommand(),
GenerateActionsCommand(),
@@ -47,6 +47,23 @@ internal class CompletionEvaluationStarter : ApplicationStarter {
ContextCollectionEvaluationCommand()
)
.main(args.toList().subList(1, args.size))
val startTimestamp = System.currentTimeMillis()
try {
run()
val delta = 5_000 - (System.currentTimeMillis() - startTimestamp)
if (delta > 0) {
Thread.sleep(delta) // for graceful shutdown
}
exit(0)
}
catch (e: FinishEvaluationStep.EvaluationCompletedWithErrorsException) {
fatalError(e.message!!)
}
catch (e: Exception) {
fatalError("Evaluation failed $e. StackTrace: ${stackTraceToString(e)}")
}
}
abstract class EvaluationCommand(name: String, help: String) : CliktCommand(name = name, help = help) {
@@ -66,37 +83,9 @@ internal class CompletionEvaluationStarter : ApplicationStarter {
protected fun runPreliminarySteps(feature: EvaluableFeature<*>, workspace: EvaluationWorkspace) {
for (step in feature.getPreliminaryEvaluationSteps()) {
println("Starting preliminary step: ${step.name}")
step.start(workspace)
step.runInIntellij(null, workspace)
}
}
protected fun loadAndApply(projectPath: String, action: (Project) -> Unit) {
val project: Project?
try {
println("Open and load project $projectPath. Operation may take a few minutes.")
@Suppress("SSBasedInspection")
project = runUnderModalProgressIfIsEdt {
importOrOpenProjectAsync(OpenProjectArgsData(FileSystems.getDefault().getPath(projectPath)))
}
println("Project loaded!")
try {
action(project)
}
catch (exception: Exception) {
throw RuntimeException("Failed to run actions on the project: $exception", exception)
}
}
catch (e: Exception) {
fatalError("Project could not be loaded or processed: $e. StackTrace: ${stackTraceToString(e)}")
}
}
private fun fatalError(msg: String): Nothing {
System.err.println("Evaluation failed: $msg")
exitProcess(1)
}
}
inner class MainEvaluationCommand : EvaluationCommand(commandNameFromExtension!!, "Evaluate code completion quality in headless mode") {
@@ -111,13 +100,14 @@ internal class CompletionEvaluationStarter : ApplicationStarter {
val feature = EvaluableFeature.forFeature(featureName) ?: throw Exception("No support for the $featureName")
val config = loadConfig(Paths.get(configPath), feature.getStrategySerializer())
val workspace = EvaluationWorkspace.create(config, SetupStatsCollectorStep.statsCollectorLogsDirectory)
val datasetContext = DatasetContext(workspace, workspace, configPath)
runPreliminarySteps(feature, workspace)
loadAndApply(config.projectPath) { project ->
val stepFactory = BackgroundStepFactory(feature, config, project, null, EvaluationRootInfo(true))
EvaluationProcess.build({
customize()
shouldReorderElements = config.reorder.useReordering
}, stepFactory).startAsync(workspace).get()
feature.prepareEnvironment(config).use { environment ->
val stepFactory = BackgroundStepFactory(feature, config, environment, null, datasetContext)
EvaluationProcess.build(environment, stepFactory) {
customize()
shouldReorderElements = config.reorder.useReordering
}.start(workspace)
}
}
@@ -150,16 +140,18 @@ internal class CompletionEvaluationStarter : ApplicationStarter {
override fun run() {
val feature = EvaluableFeature.forFeature(featureName) ?: throw Exception("No support for the feature")
val workspace = EvaluationWorkspace.open(workspacePath, SetupStatsCollectorStep.statsCollectorLogsDirectory)
val datasetContext = DatasetContext(workspace, workspace, null)
val config = workspace.readConfig(feature.getStrategySerializer())
runPreliminarySteps(feature, workspace)
loadAndApply(config.projectPath) { project ->
val process = EvaluationProcess.build({
shouldGenerateActions = false
shouldInterpretActions = interpretActions
shouldReorderElements = reorderElements
shouldGenerateReports = generateReport
}, BackgroundStepFactory(feature, config, project, null, EvaluationRootInfo(true)))
process.startAsync(workspace)
feature.prepareEnvironment(config).use { environment ->
val stepFactory = BackgroundStepFactory(feature, config, environment, null, datasetContext)
val process = EvaluationProcess.build(environment, stepFactory) {
shouldGenerateActions = false
shouldInterpretActions = interpretActions
shouldReorderElements = reorderElements
shouldGenerateReports = generateReport
}
process.start(workspace)
}
}
}
@@ -176,13 +168,13 @@ internal class CompletionEvaluationStarter : ApplicationStarter {
"COMPARING",
)
val outputWorkspace = EvaluationWorkspace.create(config, SetupStatsCollectorStep.statsCollectorLogsDirectory)
loadAndApply(config.projectPath) { project ->
val process = EvaluationProcess.build({
shouldGenerateReports = true
},
BackgroundStepFactory(feature, config, project, workspacesToCompare,
EvaluationRootInfo(true)))
process.startAsync(outputWorkspace)
val datasetContext = DatasetContext(outputWorkspace, null, null)
feature.prepareEnvironment(config).use { environment ->
val stepFactory = BackgroundStepFactory(feature, config, environment, workspacesToCompare, datasetContext)
val process = EvaluationProcess.build(environment, stepFactory) {
shouldGenerateReports = true
}
process.start(outputWorkspace)
}
}
}
@@ -212,6 +204,7 @@ internal class CompletionEvaluationStarter : ApplicationStarter {
feature.getStrategySerializer()
)
val outputWorkspace = EvaluationWorkspace.create(config, SetupStatsCollectorStep.statsCollectorLogsDirectory)
val datasetContext = DatasetContext(outputWorkspace, null, null)
for (workspacePath in workspacesToMerge) {
val workspace = EvaluationWorkspace.open(workspacePath, SetupStatsCollectorStep.statsCollectorLogsDirectory)
val sessionFiles = workspace.sessionsStorage.getSessionFiles()
@@ -220,12 +213,12 @@ internal class CompletionEvaluationStarter : ApplicationStarter {
}
}
outputWorkspace.saveMetadata()
loadAndApply(config.projectPath) { project ->
val process = EvaluationProcess.build({
shouldGenerateReports = true
},
BackgroundStepFactory(feature, config, project, null, EvaluationRootInfo(true)))
process.startAsync(outputWorkspace)
feature.prepareEnvironment(config).use { environment ->
val stepFactory = BackgroundStepFactory(feature, config, environment, null, datasetContext)
val process = EvaluationProcess.build(environment, stepFactory) {
shouldGenerateReports = true
}
process.start(outputWorkspace)
}
}
}
@@ -246,3 +239,15 @@ private fun readWorkspacesFromDirectory(directory: String): List<String> {
return result
}
private fun exit(exitCode: Int): Nothing = try {
ApplicationManagerEx.getApplicationEx().exit(FORCE_EXIT, exitCode)
throw IllegalStateException("Process should be finished!!!")
} catch (t: Throwable) {
exitProcess(exitCode)
}
private fun fatalError(msg: String): Nothing {
System.err.println("Evaluation failed: $msg")
exit(1)
}

View File

@@ -13,7 +13,6 @@ import com.intellij.cce.evaluable.StrategySerializer
import com.intellij.cce.evaluable.common.getEditorSafe
import com.intellij.cce.evaluable.completion.BaseCompletionActionsInvoker
import com.intellij.cce.evaluation.*
import com.intellij.cce.evaluation.step.ActionsGenerationStep
import com.intellij.cce.evaluation.step.SetupStatsCollectorStep
import com.intellij.cce.filter.EvaluationFilter
import com.intellij.cce.filter.EvaluationFilterReader
@@ -58,44 +57,52 @@ internal class ContextCollectionEvaluationCommand : CompletionEvaluationStarter.
val config = loadConfig(Paths.get(configPath), feature.getStrategySerializer())
val workspace = EvaluationWorkspace.create(config, SetupStatsCollectorStep.statsCollectorLogsDirectory)
val evaluationRootInfo = EvaluationRootInfo(true)
loadAndApply(config.projectPath) { project ->
val stepFactory = object : StepFactory by BackgroundStepFactory(
feature = feature,
config = config,
project = project,
inputWorkspacePaths = null,
evaluationRootInfo = evaluationRootInfo
feature.prepareEnvironment(config).use { environment ->
val dataset = environment.dataset
check(dataset is ProjectActionsDataset)
val actions = dataset.config
val newDataset = object : ProjectActionsDataset(
config.strategy,
actions,
config.interpret.filesLimit,
config.interpret.sessionsLimit,
evaluationRootInfo,
dataset.project,
dataset.processor,
feature.name
) {
override fun generateActionsStep(): EvaluationStep {
return object : ActionsGenerationStep(
config = config,
language = config.language,
evaluationRootInfo = evaluationRootInfo,
project = project,
processor = feature.getGenerateActionsProcessor(config.strategy),
featureName = feature.name
) {
override fun runInBackground(workspace: EvaluationWorkspace, progress: Progress): EvaluationWorkspace {
val files = runReadAction {
FilesHelper.getFilesOfLanguage(project, config.actions.evaluationRoots, config.actions.ignoreFileNames, language)
}.sortedBy { it.name }
val strategy = config.strategy as CompletionContextCollectionStrategy
val sampled = files.shuffled(Random(strategy.samplingSeed)).take(strategy.samplesCount).sortedBy { it.name }
generateActions(workspace, language, sampled, evaluationRootInfo, progress)
return workspace
}
}
override fun prepare(datasetContext: DatasetContext, progress: Progress) {
val files = runReadAction {
FilesHelper.getFilesOfLanguage(project, actions.evaluationRoots, actions.ignoreFileNames, actions.language)
}.sortedBy { it.name }
val strategy = config.strategy as CompletionContextCollectionStrategy
val sampled = files.shuffled(Random(strategy.samplingSeed)).take(strategy.samplesCount).sortedBy { it.name }
generateActions(datasetContext, actions.language, sampled, evaluationRootInfo, progress)
}
}
val process = EvaluationProcess.build(
init = {
shouldGenerateActions = true
shouldInterpretActions = true
shouldGenerateReports = false
shouldReorderElements = config.reorder.useReordering
},
stepFactory = stepFactory
val newEnvironment = object : EvaluationEnvironment by environment {
override val dataset: EvaluationDataset = newDataset
}
val datasetContext = DatasetContext(workspace, workspace, null)
val stepFactory = BackgroundStepFactory(
feature = feature,
environment = environment,
config = config,
inputWorkspacePaths = null,
datasetContext = datasetContext
)
val process = EvaluationProcess.build(newEnvironment, stepFactory) {
shouldGenerateActions = true
shouldInterpretActions = true
shouldGenerateReports = false
shouldReorderElements = config.reorder.useReordering
}
process.start(workspace)
}
}

View File

@@ -0,0 +1,123 @@
package com.intellij.cce.actions
import com.intellij.cce.core.Session
import com.intellij.cce.core.SimpleTokenProperties
import com.intellij.cce.core.SymbolLocation
import com.intellij.cce.core.TypeProperty
import com.intellij.cce.evaluation.EvaluationStep
import com.intellij.cce.interpreter.*
import com.intellij.cce.util.Progress
import kotlin.io.path.extension
import kotlin.io.path.isRegularFile
import kotlin.io.path.readLines
class CsvDataset(
private val datasetRef: DatasetRef,
private val chunkSize: Int,
private val targetField: String
) : EvaluationDataset {
override val setupSdk: EvaluationStep? = null
override val checkSdk: EvaluationStep? = null
override val preparationDescription: String = "Checking that CSV file exists"
override fun prepare(datasetContext: DatasetContext, progress: Progress) {
val datasetPath = datasetContext.path(datasetRef)
require(datasetPath.extension == "csv") {
"Csv dataset should have the appropriate extension: $datasetRef"
}
datasetRef.prepare(datasetContext)
check(datasetPath.isRegularFile()) {
"$datasetRef didn't create a file: $datasetPath"
}
}
override fun sessionCount(datasetContext: DatasetContext): Int = datasetContext.path(datasetRef).readLines().size - 1
override fun chunks(datasetContext: DatasetContext): Iterator<EvaluationDatasetChunk> {
val lines = datasetContext.path(datasetRef).readLines()
val dataLines = lines.subList(1, lines.size)
val names = lines.first().split(',').map { it.trim() }
var offset = 0
val result = mutableListOf<EvaluationDatasetChunk>()
for (rows in dataLines.chunked(chunkSize)) {
val presentationText = StringBuilder()
val calls = mutableListOf<CallFeature>()
for (row in rows) {
if (presentationText.isNotBlank()) {
presentationText.append("\n")
}
val values = names.zip(row.split(',').map { it.trim() }).toMap()
val features = values.filterNot { it.key == targetField }
val target = values[targetField]!!
calls += callFeature(target, presentationText.length, features)
presentationText.append("$target <- ${features.toList().joinToString(", ") { "${it.first} = ${it.second}" }}")
offset += 1
}
result += object : EvaluationDatasetChunk {
override val datasetName: String = this@CsvDataset.datasetRef.name
override val name: String = "$datasetName:${offset - rows.size + 1}-${offset}"
override val sessionCount: Int = calls.size
override val presentationText: String = presentationText.toString()
override fun evaluate(
featureInvoker: FeatureInvoker,
handler: InterpretationHandler,
filter: InterpretFilter,
order: InterpretationOrder,
sessionHandler: (Session) -> Unit
): List<Session> {
val sessions = mutableListOf<Session>()
for (call in calls.reorder(order)) {
if (!filter.shouldCompleteToken()) {
continue
}
handler.onActionStarted(call)
val session = featureInvoker.callFeature(call.expectedText, call.offset, call.nodeProperties)
sessions += session
sessionHandler(session)
if (handler.onSessionFinished(name)) {
break
}
}
handler.onFileProcessed(name)
return sessions
}
}
}
return result.iterator()
}
private fun callFeature(target: String, offset: Int, features: Map<String, String>): CallFeature {
val actions = ActionsBuilder().run {
session {
val properties = SimpleTokenProperties.create(TypeProperty.UNKNOWN, SymbolLocation.UNKNOWN) {
features.forEach { put(it.key, it.value) }
}
callFeature(target, offset, properties)
}
build()
}
val action = actions.first()
check(action is CallFeature)
return action
}
}

View File

@@ -0,0 +1,44 @@
package com.intellij.cce.actions
import com.intellij.cce.workspace.EvaluationWorkspace
import com.intellij.cce.workspace.storages.FileErrorsStorage
import com.intellij.cce.workspace.storages.storage.ActionsStorage
import com.intellij.cce.workspace.storages.storage.ActionsStorageFactory
import com.intellij.cce.workspace.storages.storage.getActionsStorageTypeFromEnv
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
/**
* Provides a context for working with datasets.
* It is supposed to have everything needed to generate and store data.
*/
class DatasetContext(
private val outputWorkspace: EvaluationWorkspace,
private val actionWorkspace: EvaluationWorkspace?,
internal val configPath: String?,
) {
private val datasetDir = Paths.get("ml-eval-datasets")
val actionsStorage: ActionsStorage by lazy {
check(actionWorkspace != null) { "It seems that workspace with actions wasn't configured" }
val directory = actionWorkspace.path().resolve("actions")
Files.createDirectories(directory)
ActionsStorageFactory.create(directory.toString(), getActionsStorageTypeFromEnv())
}
val errorsStorage: FileErrorsStorage = outputWorkspace.errorsStorage
fun saveAdditionalStats(name: String, stats: Map<String, Any>) {
outputWorkspace.saveAdditionalStats(name, stats)
}
fun path(name: String): Path {
Files.createDirectories(datasetDir)
return datasetDir.resolve(name)
}
fun path(ref: DatasetRef): Path = path(ref.name)
}

View File

@@ -0,0 +1,64 @@
package com.intellij.cce.actions
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption
import kotlin.io.path.exists
sealed interface DatasetRef {
val name: String
fun prepare(datasetContext: DatasetContext)
companion object {
private const val EXISTING_PROTOCOL = "existing:"
fun parse(ref: String): DatasetRef {
if (ref.startsWith(EXISTING_PROTOCOL)) {
return ExistingRef(ref.substring(EXISTING_PROTOCOL.length))
}
if (ref.contains(":")) {
throw IllegalArgumentException("Protocol is not supported: $ref")
}
return ConfigRelativeRef(ref)
}
}
}
internal data class ConfigRelativeRef(val relativePath: String) : DatasetRef {
override val name: String = Path.of(relativePath).normalize().toString()
override fun prepare(datasetContext: DatasetContext) {
val targetPath = datasetContext.path(this)
if (targetPath.exists()) {
return
}
val configPath = checkNotNull(datasetContext.configPath) {
"Path $relativePath supposed to be relative to config, but there is no config explicitly provided. " +
"Note that this option is only for test purposes and not supposed to be used in production."
}
val sourcePath = Path.of(configPath).parent.resolve(relativePath)
check(sourcePath.exists()) {
"Config-relative path $relativePath does not exist: $sourcePath"
}
Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING)
}
}
internal data class ExistingRef(override val name: String) : DatasetRef {
override fun prepare(datasetContext: DatasetContext) {
val path = datasetContext.path(name)
check(path.exists()) {
"Dataset $name does not exist: $path"
}
}
}

View File

@@ -0,0 +1,41 @@
package com.intellij.cce.actions
import com.intellij.cce.core.Session
import com.intellij.cce.evaluation.EvaluationStep
import com.intellij.cce.interpreter.FeatureInvoker
import com.intellij.cce.interpreter.InterpretFilter
import com.intellij.cce.interpreter.InterpretationHandler
import com.intellij.cce.interpreter.InterpretationOrder
import com.intellij.cce.util.Progress
/**
* Represents data which will be used for evaluation.
*/
interface EvaluationDataset {
val setupSdk: EvaluationStep?
val checkSdk: EvaluationStep?
val preparationDescription: String
fun prepare(datasetContext: DatasetContext, progress: Progress)
fun sessionCount(datasetContext: DatasetContext): Int
// TODO should return something closeable for large files
fun chunks(datasetContext: DatasetContext): Iterator<EvaluationDatasetChunk>
}
interface EvaluationDatasetChunk {
val datasetName: String
val name: String
val sessionCount: Int
val presentationText: String
fun evaluate(
featureInvoker: FeatureInvoker,
handler: InterpretationHandler,
filter: InterpretFilter,
order: InterpretationOrder,
sessionHandler: (Session) -> Unit
): List<Session>
}

View File

@@ -1,14 +1,14 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.cce.evaluation.step
package com.intellij.cce.actions
import com.intellij.cce.actions.ActionsGenerator
import com.intellij.cce.actions.CallFeature
import com.intellij.cce.actions.FileActions
import com.intellij.cce.core.JvmProperties
import com.intellij.cce.core.PropertyAdapters
import com.intellij.cce.core.SymbolLocation
import com.intellij.cce.core.TokenProperties
import com.intellij.cce.core.*
import com.intellij.cce.evaluable.EvaluationStrategy
import com.intellij.cce.evaluable.common.CommonActionsInvoker
import com.intellij.cce.evaluation.EvaluationRootInfo
import com.intellij.cce.evaluation.EvaluationStep
import com.intellij.cce.evaluation.SetupSdkStep
import com.intellij.cce.evaluation.step.CheckProjectSdkStep
import com.intellij.cce.interpreter.*
import com.intellij.cce.processor.DefaultEvaluationRootProcessor
import com.intellij.cce.processor.EvaluationRootByRangeProcessor
import com.intellij.cce.processor.GenerateActionsProcessor
@@ -16,44 +16,59 @@ import com.intellij.cce.util.ExceptionsUtil.stackTraceToString
import com.intellij.cce.util.FilesHelper
import com.intellij.cce.util.Progress
import com.intellij.cce.util.Summary
import com.intellij.cce.util.text
import com.intellij.cce.visitor.CodeFragmentBuilder
import com.intellij.cce.workspace.Config
import com.intellij.cce.workspace.EvaluationWorkspace
import com.intellij.cce.workspace.info.FileErrorInfo
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import kotlin.random.Random
open class ActionsGenerationStep(
protected val config: Config,
protected val language: String,
protected val evaluationRootInfo: EvaluationRootInfo,
project: Project,
protected val processor: GenerateActionsProcessor,
protected val featureName: String
) : BackgroundEvaluationStep(project) {
override val name: String = "Generating actions"
open class ProjectActionsDataset(
private val strategy: EvaluationStrategy,
val config: Config.ActionsGeneration,
private val filesLimit: Int?, // TODO dataset generation could be lazy
private val sessionsLimit: Int?,
private val evaluationRootInfo: EvaluationRootInfo,
val project: Project,
val processor: GenerateActionsProcessor,
private val featureName: String
) : EvaluationDataset {
override val setupSdk: EvaluationStep? = SetupSdkStep.forLanguage(project, Language.resolve(config.language))
override val checkSdk: EvaluationStep? = CheckProjectSdkStep(project, config.language)
override val description: String = "Generating actions by selected files"
override val preparationDescription: String = "Generating actions by selected files"
override fun runInBackground(workspace: EvaluationWorkspace, progress: Progress): EvaluationWorkspace {
override fun prepare(datasetContext: DatasetContext, progress: Progress) {
val filesForEvaluation = ReadAction.compute<List<VirtualFile>, Throwable> {
FilesHelper.getFilesOfLanguage(project, config.actions.evaluationRoots, config.actions.ignoreFileNames, language)
FilesHelper.getFilesOfLanguage(project, config.evaluationRoots, config.ignoreFileNames, config.language)
}
generateActions(
workspace,
language,
datasetContext,
config.language,
filesForEvaluation,
evaluationRootInfo,
progress,
filesLimit = config.interpret.filesLimit ?: Int.MAX_VALUE,
sessionsLimit = config.interpret.sessionsLimit ?: Int.MAX_VALUE,)
return workspace
filesLimit = this.filesLimit ?: Int.MAX_VALUE,
sessionsLimit = this.sessionsLimit ?: Int.MAX_VALUE,
)
}
override fun sessionCount(datasetContext: DatasetContext): Int = datasetContext.actionsStorage.computeSessionsCount()
override fun chunks(datasetContext: DatasetContext): Iterator<EvaluationDatasetChunk> {
val files = datasetContext.actionsStorage.getActionFiles()
return files.shuffled(FILES_RANDOM).asSequence().map { file ->
val fileActions = datasetContext.actionsStorage.getActions(file)
val fileText = FilesHelper.getFile(project, fileActions.path).text()
FileActionsChunk(fileActions, fileText)
}.iterator()
}
protected fun generateActions(
workspace: EvaluationWorkspace,
datasetContext: DatasetContext,
languageName: String,
files: Collection<VirtualFile>,
evaluationRootInfo: EvaluationRootInfo,
@@ -62,7 +77,7 @@ open class ActionsGenerationStep(
sessionsLimit: Int = Int.MAX_VALUE,
) {
val actionsGenerator = ActionsGenerator(processor)
val codeFragmentBuilder = CodeFragmentBuilder.create(project, languageName, featureName, config.strategy)
val codeFragmentBuilder = CodeFragmentBuilder.create(project, languageName, featureName, strategy)
val errors = mutableListOf<FileErrorInfo>()
var totalSessions = 0
@@ -96,7 +111,7 @@ open class ActionsGenerationStep(
val codeFragment = codeFragmentBuilder.build(file, rootVisitor, featureName)
val fileActions = actionsGenerator.generate(codeFragment)
actionsSummarizer.update(fileActions)
workspace.actionsStorage.saveActions(fileActions)
datasetContext.actionsStorage.saveActions(fileActions)
totalSessions += fileActions.sessionsCount
if (fileActions.sessionsCount > 0) {
totalFiles++
@@ -106,7 +121,7 @@ open class ActionsGenerationStep(
catch (e: Throwable) {
indicator.setProgress(filename, "error: ${e.message} | $filename", progress)
try {
workspace.errorsStorage.saveError(
datasetContext.errorsStorage.saveError(
FileErrorInfo(FilesHelper.getRelativeToProjectPath(project, file.path), e.message
?: "No Message", stackTraceToString(e))
)
@@ -120,7 +135,7 @@ open class ActionsGenerationStep(
LOG.info("Generating actions for file ${file.path} completed. Done: $i/${files.size}. With error: ${errors.size}")
}
actionsSummarizer.save(workspace)
actionsSummarizer.save(datasetContext)
}
private class ActionsSummarizer {
@@ -164,8 +179,8 @@ open class ActionsGenerationStep(
}
}
fun save(workspace: EvaluationWorkspace) {
workspace.saveAdditionalStats("actions", rootSummary.asSerializable())
fun save(datasetContext: DatasetContext) {
datasetContext.saveAdditionalStats("actions", rootSummary.asSerializable())
}
private enum class CompletionKind {
@@ -181,6 +196,31 @@ open class ActionsGenerationStep(
private fun TokenProperties.java(): JvmProperties? = PropertyAdapters.Jvm.adapt(this)
}
private inner class FileActionsChunk(
private val fileActions: FileActions,
override val presentationText: String,
) : EvaluationDatasetChunk {
override val datasetName: String = config.projectName
override val name: String = fileActions.path
override val sessionCount: Int = fileActions.sessionsCount
override fun evaluate(
featureInvoker: FeatureInvoker,
handler: InterpretationHandler,
filter: InterpretFilter,
order: InterpretationOrder,
sessionHandler: (Session) -> Unit
): List<Session> {
val factory = object : InvokersFactory {
override fun createActionsInvoker(): ActionsInvoker = CommonActionsInvoker(project)
override fun createFeatureInvoker(): FeatureInvoker = featureInvoker
}
val actionInterpreter = ActionInvokingInterpreter(factory, handler, filter, order)
return actionInterpreter.interpret(fileActions, sessionHandler)
}
}
}
private val LOG = Logger.getInstance(ProjectActionsDataset::class.java)
private val FILES_RANDOM = Random(42)

View File

@@ -1,17 +1,15 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.cce.evaluable
import com.intellij.cce.core.Language
import com.intellij.cce.evaluation.EvaluationEnvironment
import com.intellij.cce.evaluation.EvaluationStep
import com.intellij.cce.interpreter.FeatureInvoker
import com.intellij.cce.metric.Metric
import com.intellij.cce.processor.GenerateActionsProcessor
import com.intellij.cce.report.FileReportGenerator
import com.intellij.cce.report.GeneratorDirectories
import com.intellij.cce.workspace.Config
import com.intellij.cce.workspace.storages.FeaturesStorage
import com.intellij.cce.workspace.storages.FullLineLogsStorage
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project
/**
* Represents a feature that can be evaluated in IDE.
@@ -26,14 +24,9 @@ interface EvaluableFeature<T : EvaluationStrategy> {
fun getStrategySerializer(): StrategySerializer<T>
/**
* how to prepare the context before the feature invocation
* @return initialized environment which will be used during evaluation
*/
fun getGenerateActionsProcessor(strategy: T): GenerateActionsProcessor
/**
* how to call the feature
*/
fun getFeatureInvoker(project: Project, language: Language, strategy: T): FeatureInvoker
fun prepareEnvironment(config: Config): EvaluationEnvironment
/**
* how to render the results of evaluation
@@ -52,11 +45,11 @@ interface EvaluableFeature<T : EvaluationStrategy> {
/**
* additional steps to set up evaluation
*/
fun getEvaluationSteps(language: Language, strategy: T): List<EvaluationStep>
fun getEvaluationSteps(config: Config): List<EvaluationStep>
/**
* additional steps to set up evaluation before project is opened
* additional steps to set up evaluation before environment is initialized
*/
fun getPreliminaryEvaluationSteps(): List<EvaluationStep>

View File

@@ -1,15 +1,36 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.cce.evaluable
import com.intellij.cce.evaluation.EvaluationStep
import com.intellij.cce.actions.ProjectActionsDataset
import com.intellij.cce.core.Language
import com.intellij.cce.evaluation.*
import com.intellij.cce.interpreter.FeatureInvoker
import com.intellij.cce.processor.GenerateActionsProcessor
import com.intellij.cce.report.BasicFileReportGenerator
import com.intellij.cce.report.FileReportGenerator
import com.intellij.cce.report.GeneratorDirectories
import com.intellij.cce.workspace.Config
import com.intellij.cce.workspace.storages.FeaturesStorage
import com.intellij.cce.workspace.storages.FullLineLogsStorage
import com.intellij.openapi.project.Project
abstract class EvaluableFeatureBase<T : EvaluationStrategy>(override val name: String) : EvaluableFeature<T> {
/**
* how to prepare the context before the feature invocation
*/
abstract fun getGenerateActionsProcessor(strategy: T): GenerateActionsProcessor
/**
* how to call the feature
*/
abstract fun getFeatureInvoker(project: Project, language: Language, strategy: T): FeatureInvoker
abstract fun getEvaluationSteps(language: Language, strategy: T): List<EvaluationStep>
override fun getEvaluationSteps(config: Config): List<EvaluationStep> =
getEvaluationSteps(Language.resolve(actions(config).language), config.strategy())
override fun getFileReportGenerator(filterName: String,
comparisonFilterName: String,
featuresStorages: List<FeaturesStorage>,
@@ -18,4 +39,27 @@ abstract class EvaluableFeatureBase<T : EvaluationStrategy>(override val name: S
BasicFileReportGenerator(filterName, comparisonFilterName, featuresStorages, dirs)
override fun getPreliminaryEvaluationSteps(): List<EvaluationStep> = emptyList()
override fun prepareEnvironment(config: Config): EvaluationEnvironment {
val actions = actions(config)
val strategy = config.strategy<T>()
return ProjectEnvironment.open(actions.projectPath) { project ->
StandaloneEnvironment(
dataset = ProjectActionsDataset(
strategy,
actions,
config.interpret.filesLimit,
config.interpret.sessionsLimit,
EvaluationRootInfo(true),
project,
getGenerateActionsProcessor(strategy),
name
),
featureInvoker = getFeatureInvoker(project, Language.resolve(actions.language), strategy)
)
}
}
private fun actions(config: Config) =
config.actions ?: throw IllegalStateException("Configuration missing project description (actions)")
}

View File

@@ -0,0 +1,41 @@
package com.intellij.cce.evaluable
import com.intellij.cce.actions.EvaluationDataset
import com.intellij.cce.evaluation.EvaluationEnvironment
import com.intellij.cce.evaluation.EvaluationStep
import com.intellij.cce.evaluation.StandaloneEnvironment
import com.intellij.cce.interpreter.FeatureInvoker
import com.intellij.cce.report.BasicFileReportGenerator
import com.intellij.cce.report.FileReportGenerator
import com.intellij.cce.report.GeneratorDirectories
import com.intellij.cce.workspace.Config
import com.intellij.cce.workspace.storages.FeaturesStorage
import com.intellij.cce.workspace.storages.FullLineLogsStorage
abstract class StandaloneFeature<T : EvaluationStrategy>(
override val name: String
) : EvaluableFeature<T> {
abstract fun getDataset(config: Config): EvaluationDataset
abstract fun getFeatureInvoker(strategy: T): FeatureInvoker
override fun getPreliminaryEvaluationSteps(): List<EvaluationStep> = emptyList()
override fun getEvaluationSteps(config: Config): List<EvaluationStep> = emptyList()
override fun getFileReportGenerator(
filterName: String,
comparisonFilterName: String,
featuresStorages: List<FeaturesStorage>,
fullLineStorages: List<FullLineLogsStorage>,
dirs: GeneratorDirectories
): FileReportGenerator = BasicFileReportGenerator(filterName, comparisonFilterName, featuresStorages, dirs)
override fun prepareEnvironment(config: Config): EvaluationEnvironment {
return StandaloneEnvironment(
getDataset(config),
getFeatureInvoker(config.strategy())
)
}
}

View File

@@ -25,6 +25,7 @@ import com.intellij.psi.util.PsiTreeUtil
import com.intellij.testFramework.TestModeFlags
import com.intellij.util.progress.sleepCancellable
import java.io.File
import java.nio.file.Paths
class CommonActionsInvoker(private val project: Project) : ActionsInvoker {
init {
@@ -103,24 +104,24 @@ class CommonActionsInvoker(private val project: Project) : ActionsInvoker {
override fun openFile(file: String): String = readActionInSmartMode(project) {
LOG.info("Open file: $file")
val virtualFile = LocalFileSystem.getInstance().findFileByIoFile(File(file))
val virtualFile = LocalFileSystem.getInstance().findFileByIoFile(File(fullPath(file)))
val descriptor = OpenFileDescriptor(project, virtualFile!!)
spaceStrippingEnabled = TrailingSpacesStripper.isEnabled(virtualFile)
TrailingSpacesStripper.setEnabled(virtualFile, false)
val fileEditor = FileEditorManager.getInstance(project).openTextEditor(descriptor, true)
?: throw Exception("Can't open text editor for file: $file")
?: throw Exception("Can't open text editor for file: ${fullPath(file)}")
return@readActionInSmartMode fileEditor.document.text
}
override fun closeFile(file: String) = onEdt {
LOG.info("Close file: $file")
val virtualFile = LocalFileSystem.getInstance().findFileByIoFile(File(file))!!
val virtualFile = LocalFileSystem.getInstance().findFileByIoFile(File(fullPath(file)))!!
TrailingSpacesStripper.setEnabled(virtualFile, spaceStrippingEnabled)
FileEditorManager.getInstance(project).closeFile(virtualFile)
}
override fun isOpen(file: String): Boolean = readActionInSmartMode(project) {
FileEditorManager.getInstance(project).openFiles.any { it.path == file }
FileEditorManager.getInstance(project).openFiles.any { it.path == fullPath(file) }
}
override fun save() = writeAction {
@@ -140,6 +141,8 @@ class CommonActionsInvoker(private val project: Project) : ActionsInvoker {
action()
}
private fun fullPath(subPath: String) = project.basePath?.let { Paths.get(it).resolve(subPath).toString() } ?: subPath
companion object {
private val LOG = logger<CommonActionsInvoker>()
private const val LOG_MAX_LENGTH = 50

View File

@@ -0,0 +1,45 @@
package com.intellij.cce.evaluable.standaloneExample
import com.google.gson.JsonObject
import com.google.gson.JsonSerializationContext
import com.intellij.cce.actions.CsvDataset
import com.intellij.cce.actions.DatasetRef
import com.intellij.cce.actions.EvaluationDataset
import com.intellij.cce.evaluable.EvaluationStrategy
import com.intellij.cce.evaluable.StandaloneFeature
import com.intellij.cce.evaluable.StrategySerializer
import com.intellij.cce.filter.EvaluationFilter
import com.intellij.cce.interpreter.FeatureInvoker
import com.intellij.cce.metric.Metric
import com.intellij.cce.metric.PrecisionMetric
import com.intellij.cce.metric.SessionsCountMetric
import com.intellij.cce.workspace.Config
import java.lang.reflect.Type
class StandaloneExampleFeature : StandaloneFeature<DatasetStrategy>("standalone-example") {
override fun getStrategySerializer(): StrategySerializer<DatasetStrategy> = object : StrategySerializer<DatasetStrategy> {
override fun serialize(src: DatasetStrategy, typeOfSrc: Type, context: JsonSerializationContext): JsonObject = JsonObject()
override fun deserialize(map: Map<String, Any>, language: String): DatasetStrategy = DatasetStrategy()
}
override fun getDataset(config: Config): EvaluationDataset {
val fileDataset = config.fileDataset ?: throw IllegalStateException("Required dataset config")
return CsvDataset(
datasetRef = DatasetRef.parse(fileDataset.url),
chunkSize = fileDataset.chunkSize,
targetField = "Type"
)
}
override fun getFeatureInvoker(strategy: DatasetStrategy): FeatureInvoker = StandaloneExampleInvoker()
override fun getMetrics(): List<Metric> = listOf(
SessionsCountMetric(),
PrecisionMetric()
)
}
class DatasetStrategy : EvaluationStrategy {
override val filters: Map<String, EvaluationFilter> = emptyMap()
}

View File

@@ -0,0 +1,45 @@
package com.intellij.cce.evaluable.standaloneExample
import com.intellij.cce.core.*
import com.intellij.cce.interpreter.FeatureInvoker
class StandaloneExampleInvoker : FeatureInvoker {
override fun callFeature(expectedText: String, offset: Int, properties: TokenProperties): Session {
val session = Session(
offset = offset,
expectedText = expectedText,
completableLength = expectedText.length,
properties,
)
val letter = properties.additionalProperty("Letter")!!.first()
val text = if (letter.code % 2 == 1) "Vowel" else "Consonant"
val suggestions = listOf(
Suggestion(
text = text,
presentationText = text,
source = SuggestionSource.INTELLIJ,
details = emptyMap(),
isRelevant = text == expectedText
)
)
val lookup = Lookup(
"",
offset,
suggestions,
latency = 0,
features = null,
selectedPosition = suggestions.indexOfFirst { it.isRelevant },
isNew = false,
additionalInfo = emptyMap()
)
session.addLookup(lookup)
return session
}
override fun comparator(generated: String, expected: String): Boolean = generated == expected
}

View File

@@ -1,12 +1,14 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.cce.evaluation
import com.intellij.cce.actions.DatasetContext
import com.intellij.cce.actions.EvaluationDataset
import com.intellij.cce.evaluation.step.SetupStatsCollectorStep
import com.intellij.cce.interpreter.*
import com.intellij.cce.interpreter.FeatureInvoker
import com.intellij.cce.interpreter.InterpretFilter
import com.intellij.cce.interpreter.InterpretationHandlerImpl
import com.intellij.cce.util.ExceptionsUtil
import com.intellij.cce.util.FilesHelper
import com.intellij.cce.util.Progress
import com.intellij.cce.util.text
import com.intellij.cce.workspace.Config
import com.intellij.cce.workspace.EvaluationWorkspace
import com.intellij.cce.workspace.info.FileErrorInfo
@@ -16,16 +18,14 @@ import com.intellij.cce.workspace.storages.LogsSaver
import com.intellij.cce.workspace.storages.asCompositeLogsSaver
import com.intellij.cce.workspace.storages.logsSaverIf
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import kotlin.math.roundToInt
import kotlin.random.Random
import kotlin.system.measureTimeMillis
class ActionsInterpretationHandler(
private val config: Config,
private val language: String,
private val invokersFactory: InvokersFactory,
private val project: Project) : TwoWorkspaceHandler {
private val datasetContext: DatasetContext,
private val featureInvoker: FeatureInvoker,
) {
companion object {
val LOG = Logger.getInstance(ActionsInterpretationHandler::class.java)
@@ -36,44 +36,57 @@ class ActionsInterpretationHandler(
logsSaverIf(config.interpret.saveFusLogs) { workspace.fusLogsSaver }
).asCompositeLogsSaver()
override fun invoke(workspace1: EvaluationWorkspace, workspace2: EvaluationWorkspace, indicator: Progress) {
fun invoke(dataset: EvaluationDataset, workspace: EvaluationWorkspace, indicator: Progress) {
var sessionsCount: Int
val computingTime = measureTimeMillis {
sessionsCount = workspace1.actionsStorage.computeSessionsCount()
sessionsCount = dataset.sessionCount(datasetContext)
}
LOG.info("Computing of sessions count took $computingTime ms")
val interpretationConfig = config.interpret
val logsSaver = createLogsSaver(workspace2)
val logsSaver = createLogsSaver(workspace)
val handler = InterpretationHandlerImpl(indicator, sessionsCount, interpretationConfig.sessionsLimit)
val filter =
if (interpretationConfig.sessionProbability < 1)
RandomInterpretFilter(interpretationConfig.sessionProbability, interpretationConfig.sessionSeed)
else InterpretFilter.default()
val interpreter = ActionInvokingInterpreter(invokersFactory, handler, filter, config.interpret.order, project.basePath)
.wrapLogging(logsSaver)
val featuresStorage = if (interpretationConfig.saveFeatures) workspace2.featuresStorage else FeaturesStorage.EMPTY
val featuresStorage = if (interpretationConfig.saveFeatures) workspace.featuresStorage else FeaturesStorage.EMPTY
LOG.info("Start interpreting actions")
if (interpretationConfig.sessionProbability < 1) {
val skippedSessions = (sessionsCount * (1.0 - interpretationConfig.sessionProbability)).roundToInt()
println("During actions interpretation will be skipped about $skippedSessions sessions")
}
val files = workspace1.actionsStorage.getActionFiles()
for (file in files.shuffled(FILES_RANDOM)) {
val fileActions = workspace1.actionsStorage.getActions(file)
workspace2.fullLineLogsStorage.enableLogging(fileActions.path)
var fileCount = 0
for (chunk in dataset.chunks(datasetContext)) {
if (config.interpret.filesLimit?.let { it <= fileCount } == true) {
break
}
workspace.fullLineLogsStorage.enableLogging(chunk.name)
try {
val sessions = interpreter.interpret(fileActions) { session -> featuresStorage.saveSession(session, fileActions.path) }
val sessions = logsSaver.invokeRememberingLogs {
chunk.evaluate(featureInvoker, handler, filter, interpretationConfig.order) { session ->
featuresStorage.saveSession(session, chunk.name)
}
}
if (sessions.isNotEmpty()) {
val fileText = FilesHelper.getFile(project, fileActions.path).text()
workspace2.sessionsStorage.saveSessions(FileSessionsInfo(config.projectName, fileActions.path, fileText, sessions))
} else {
LOG.warn("No sessions collected from file: $file")
val sessionsInfo = FileSessionsInfo(
projectName = chunk.datasetName,
filePath = chunk.name,
text = chunk.presentationText,
sessions = sessions
)
workspace.sessionsStorage.saveSessions(sessionsInfo)
fileCount += 1
}
else {
LOG.warn("No sessions collected from file: ${chunk.name}")
}
}
catch (e: Throwable) {
try {
workspace2.errorsStorage.saveError(
FileErrorInfo(fileActions.path, e.message ?: "No Message", ExceptionsUtil.stackTraceToString(e))
workspace.errorsStorage.saveError(
FileErrorInfo(chunk.name, e.message ?: "No Message", ExceptionsUtil.stackTraceToString(e))
)
}
catch (e2: Throwable) {
@@ -83,11 +96,9 @@ class ActionsInterpretationHandler(
}
if (handler.isCancelled() || handler.isLimitExceeded()) break
}
logsSaver.save(language, config.interpret.trainTestSplit)
logsSaver.save(config.actions?.language, config.interpret.trainTestSplit)
SetupStatsCollectorStep.deleteLogs()
workspace2.saveMetadata()
workspace.saveMetadata()
LOG.info("Interpreting actions completed")
}
}
private val FILES_RANDOM = Random(42)

View File

@@ -1,49 +1,36 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.cce.evaluation
import com.intellij.cce.core.Language
import com.intellij.cce.actions.DatasetContext
import com.intellij.cce.evaluable.EvaluableFeature
import com.intellij.cce.evaluable.EvaluationStrategy
import com.intellij.cce.evaluable.common.CommonActionsInvoker
import com.intellij.cce.evaluation.step.*
import com.intellij.cce.interpreter.ActionsInvoker
import com.intellij.cce.interpreter.FeatureInvoker
import com.intellij.cce.interpreter.InvokersFactory
import com.intellij.cce.workspace.Config
import com.intellij.cce.workspace.EvaluationWorkspace
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.Project
class BackgroundStepFactory(
private val feature: EvaluableFeature<EvaluationStrategy>,
private val config: Config,
private val project: Project,
private val environment: EvaluationEnvironment,
private val inputWorkspacePaths: List<String>?,
private val evaluationRootInfo: EvaluationRootInfo
private val datasetContext: DatasetContext
) : StepFactory {
private val invokersFactory = object : InvokersFactory {
override fun createActionsInvoker(): ActionsInvoker = CommonActionsInvoker(project)
override fun createFeatureInvoker(): FeatureInvoker = feature.getFeatureInvoker(project, Language.resolve(config.language), config.strategy)
}
override fun generateActionsStep(): EvaluationStep {
return ActionsGenerationStep(config, config.language, evaluationRootInfo,
project, feature.getGenerateActionsProcessor(config.strategy), feature.name)
}
override fun generateActionsStep(): EvaluationStep = DatasetPreparationStep(environment.dataset, datasetContext)
override fun interpretActionsStep(): EvaluationStep =
ActionsInterpretationStep(config, config.language, invokersFactory, project)
ActionsInterpretationStep(config, environment.dataset, datasetContext, environment.featureInvoker, newWorkspace = false)
override fun generateReportStep(): EvaluationStep =
ReportGenerationStep(inputWorkspacePaths?.map { EvaluationWorkspace.open(it, SetupStatsCollectorStep.statsCollectorLogsDirectory) },
config.reports.sessionsFilters, config.reports.comparisonFilters, project, feature)
config.reports.sessionsFilters, config.reports.comparisonFilters, feature)
override fun interpretActionsOnNewWorkspaceStep(): EvaluationStep =
ActionsInterpretationOnNewWorkspaceStep(config, invokersFactory, project)
ActionsInterpretationStep(config, environment.dataset, datasetContext, environment.featureInvoker, newWorkspace = true)
override fun reorderElements(): EvaluationStep =
ReorderElementsStep(config, project)
ReorderElementsStep(config)
override fun setupStatsCollectorStep(): EvaluationStep? =
if ((config.interpret.saveLogs || config.interpret.saveFeatures || config.interpret.experimentGroup != null)
@@ -52,14 +39,13 @@ class BackgroundStepFactory(
SetupStatsCollectorStep(config.interpret.experimentGroup, config.interpret.logLocationAndItemText)
else null
override fun setupSdkStep(): EvaluationStep? = SetupSdkStep.forLanguage(project, Language.resolve(config.language))
override fun setupSdkStep(): EvaluationStep? = environment.dataset.setupSdk
override fun checkSdkConfiguredStep(): EvaluationStep = CheckProjectSdkStep(project, config.language)
override fun checkSdkConfiguredStep(): EvaluationStep? = environment.dataset.checkSdk
override fun finishEvaluationStep(): FinishEvaluationStep = HeadlessFinishEvaluationStep(project)
override fun finishEvaluationStep(): FinishEvaluationStep = HeadlessFinishEvaluationStep()
override fun featureSpecificSteps(): List<EvaluationStep> =
feature.getEvaluationSteps(Language.resolve(config.language), config.strategy)
override fun featureSpecificSteps(): List<EvaluationStep> = feature.getEvaluationSteps(config)
override fun featureSpecificPreliminarySteps(): List<EvaluationStep> = feature.getPreliminaryEvaluationSteps()
}

View File

@@ -0,0 +1,32 @@
package com.intellij.cce.evaluation
import com.intellij.cce.actions.EvaluationDataset
import com.intellij.cce.evaluation.step.runInIntellij
import com.intellij.cce.interpreter.FeatureInvoker
import com.intellij.cce.workspace.EvaluationWorkspace
/**
* Environment represents resources needed for an evaluation.
* For example, it can be IntelliJ project like in [ProjectEnvironment] if an evaluation needs an opened project.
* It should be initialized before an evaluation process and closed right after finish.
*/
interface EvaluationEnvironment : AutoCloseable {
val dataset: EvaluationDataset
val featureInvoker: FeatureInvoker
fun execute(step: EvaluationStep, workspace: EvaluationWorkspace): EvaluationWorkspace?
}
/**
* A special type of environment which doesn't imply any associated resources and can be treated like a simple data class.
*/
class StandaloneEnvironment(
override val dataset: EvaluationDataset,
override val featureInvoker: FeatureInvoker
) : EvaluationEnvironment {
override fun execute(step: EvaluationStep, workspace: EvaluationWorkspace): EvaluationWorkspace? =
step.runInIntellij(null, workspace)
override fun close() {
}
}

View File

@@ -6,20 +6,19 @@ import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.util.registry.Registry
import kotlin.system.measureTimeMillis
class EvaluationProcess private constructor(private val steps: List<EvaluationStep>,
private val finalStep: FinishEvaluationStep?) {
class EvaluationProcess private constructor (
private val environment: EvaluationEnvironment,
private val steps: List<EvaluationStep>,
private val finalStep: FinishEvaluationStep?
) {
companion object {
fun build(init: Builder.() -> Unit, stepFactory: StepFactory): EvaluationProcess {
fun build(environment: EvaluationEnvironment, stepFactory: StepFactory, init: Builder.() -> Unit): EvaluationProcess {
val builder = Builder()
builder.init()
return builder.build(stepFactory)
return builder.build(environment, stepFactory)
}
}
fun startAsync(workspace: EvaluationWorkspace) = ApplicationManager.getApplication().executeOnPooledThread {
start(workspace)
}
fun start(workspace: EvaluationWorkspace): EvaluationWorkspace {
val stats = mutableMapOf<String, Long>()
var currentWorkspace = workspace
@@ -28,7 +27,7 @@ class EvaluationProcess private constructor(private val steps: List<EvaluationSt
if (hasError && step !is UndoableEvaluationStep.UndoStep) continue
println("Starting step: ${step.name} (${step.description})")
val duration = measureTimeMillis {
val result = step.start(currentWorkspace)
val result = environment.execute(step, workspace)
if (result == null) {
hasError = true
} else {
@@ -48,7 +47,7 @@ class EvaluationProcess private constructor(private val steps: List<EvaluationSt
var shouldGenerateReports: Boolean = false
var shouldReorderElements: Boolean = false
fun build(factory: StepFactory): EvaluationProcess {
fun build(environment: EvaluationEnvironment, factory: StepFactory): EvaluationProcess {
val steps = mutableListOf<EvaluationStep>()
val isTestingEnvironment = ApplicationManager.getApplication().isUnitTestMode
@@ -56,7 +55,9 @@ class EvaluationProcess private constructor(private val steps: List<EvaluationSt
factory.setupSdkStep()?.let { steps.add(it) }
if (!Registry.`is`("evaluation.plugin.disable.sdk.check")) {
steps.add(factory.checkSdkConfiguredStep())
factory.checkSdkConfiguredStep()?.let {
steps.add(it)
}
}
}
@@ -96,7 +97,7 @@ class EvaluationProcess private constructor(private val steps: List<EvaluationSt
steps.add(step.undoStep())
}
return EvaluationProcess(steps, factory.finishEvaluationStep().takeIf { !isTestingEnvironment })
return EvaluationProcess(environment, steps, factory.finishEvaluationStep().takeIf { !isTestingEnvironment })
}
}
}

View File

@@ -9,7 +9,7 @@ class FilteredSessionsStorage(private val filter: SessionsFilter, private val st
override fun getSessions(path: String): FileSessionsInfo {
val sessionsInfo = storage.getSessions(path)
val filteredSessions = filter.apply(sessionsInfo.sessions)
return FileSessionsInfo(sessionsInfo.projectName, sessionsInfo.filePath, sessionsInfo.text, filteredSessions)
return sessionsInfo.copy(sessions = filteredSessions)
}
override fun getSessionFiles(): List<Pair<String, String>> = storage.getSessionFiles()

View File

@@ -0,0 +1,51 @@
package com.intellij.cce.evaluation
import com.intellij.cce.actions.EvaluationDataset
import com.intellij.cce.actions.OpenProjectArgsData
import com.intellij.cce.actions.ProjectOpeningUtils
import com.intellij.cce.evaluation.step.runInIntellij
import com.intellij.cce.interpreter.FeatureInvoker
import com.intellij.cce.workspace.EvaluationWorkspace
import com.intellij.ide.impl.runUnderModalProgressIfIsEdt
import com.intellij.openapi.project.Project
import com.intellij.warmup.util.importOrOpenProjectAsync
import java.nio.file.FileSystems
class ProjectEnvironment(
val project: Project,
override val dataset: EvaluationDataset,
override val featureInvoker: FeatureInvoker,
) : EvaluationEnvironment {
override fun execute(step: EvaluationStep, workspace: EvaluationWorkspace): EvaluationWorkspace? =
step.runInIntellij(project, workspace)
override fun close() {
ProjectOpeningUtils.closeProject(project)
}
companion object {
fun open(projectPath: String, init: (Project) -> StandaloneEnvironment): ProjectEnvironment {
println("Open and load project $projectPath. Operation may take a few minutes.")
@Suppress("DEPRECATION")
val project = runUnderModalProgressIfIsEdt {
importOrOpenProjectAsync(OpenProjectArgsData(FileSystems.getDefault().getPath(projectPath)))
}
println("Project loaded!")
val environment = try {
init(project)
}
catch (exception: Exception) {
ProjectOpeningUtils.closeProject(project)
throw RuntimeException("Failed to initialize project environment: $exception", exception)
}
return ProjectEnvironment(
project,
environment.dataset,
environment.featureInvoker
)
}
}
}

View File

@@ -9,7 +9,7 @@ interface StepFactory {
fun generateReportStep(): EvaluationStep
fun setupStatsCollectorStep(): EvaluationStep?
fun setupSdkStep(): EvaluationStep?
fun checkSdkConfiguredStep(): EvaluationStep
fun checkSdkConfiguredStep(): EvaluationStep?
fun finishEvaluationStep(): FinishEvaluationStep
fun featureSpecificSteps(): List<EvaluationStep>
fun featureSpecificPreliminarySteps(): List<EvaluationStep>

View File

@@ -1,20 +0,0 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.cce.evaluation.step
import com.intellij.cce.evaluation.ActionsInterpretationHandler
import com.intellij.cce.interpreter.InvokersFactory
import com.intellij.cce.workspace.Config
import com.intellij.openapi.project.Project
class ActionsInterpretationOnNewWorkspaceStep(config: Config,
invokersFactory: InvokersFactory,
project: Project) :
CreateWorkspaceStep(
config,
ActionsInterpretationHandler(config, config.language, invokersFactory, project),
project) {
override val name: String = "Actions interpreting"
override val description: String = "Interpretation of generated actions"
}

View File

@@ -1,24 +1,30 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.cce.evaluation.step
import com.intellij.cce.actions.DatasetContext
import com.intellij.cce.actions.EvaluationDataset
import com.intellij.cce.evaluation.ActionsInterpretationHandler
import com.intellij.cce.interpreter.InvokersFactory
import com.intellij.cce.interpreter.FeatureInvoker
import com.intellij.cce.util.Progress
import com.intellij.cce.workspace.Config
import com.intellij.cce.workspace.EvaluationWorkspace
import com.intellij.openapi.project.Project
class ActionsInterpretationStep(
private val config: Config,
private val language: String,
private val invokersFactory: InvokersFactory,
project: Project) : BackgroundEvaluationStep(project) {
private val dataset: EvaluationDataset,
private val datasetContext: DatasetContext,
private val featureInvoker: FeatureInvoker,
private val newWorkspace: Boolean
) : BackgroundEvaluationStep {
override val name: String = "Actions interpreting"
override val description: String = "Interpretation of generated actions"
override fun runInBackground(workspace: EvaluationWorkspace, progress: Progress): EvaluationWorkspace {
ActionsInterpretationHandler(config, language, invokersFactory, project).invoke(workspace, workspace, progress)
return workspace
val resultWorkspace =
if (newWorkspace) EvaluationWorkspace.create(config, SetupStatsCollectorStep.statsCollectorLogsDirectory)
else workspace
ActionsInterpretationHandler(config, datasetContext, featureInvoker).invoke(dataset, resultWorkspace, progress)
return resultWorkspace
}
}

View File

@@ -2,13 +2,13 @@
package com.intellij.cce.evaluation.step
import com.intellij.cce.evaluation.EvaluationStep
import com.intellij.cce.evaluation.ForegroundEvaluationStep
import com.intellij.cce.evaluation.HeadlessEvaluationAbortHandler
import com.intellij.cce.util.CommandLineProgress
import com.intellij.cce.util.Progress
import com.intellij.cce.util.TeamcityProgress
import com.intellij.cce.util.isUnderTeamCity
import com.intellij.cce.workspace.EvaluationWorkspace
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
@@ -16,40 +16,42 @@ import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator
import com.intellij.openapi.project.Project
import com.intellij.util.concurrency.FutureResult
abstract class BackgroundEvaluationStep(val project: Project) : EvaluationStep {
protected companion object {
val LOG = Logger.getInstance(BackgroundEvaluationStep::class.java)
}
interface BackgroundEvaluationStep : EvaluationStep {
fun runInBackground(workspace: EvaluationWorkspace, progress: Progress): EvaluationWorkspace
}
abstract fun runInBackground(workspace: EvaluationWorkspace, progress: Progress): EvaluationWorkspace
fun EvaluationStep.runInIntellij(project: Project?, workspace: EvaluationWorkspace): EvaluationWorkspace? {
return when (this) {
is ForegroundEvaluationStep -> start(workspace)
is BackgroundEvaluationStep -> {
val result = FutureResult<EvaluationWorkspace?>()
val task = object : Task.Backgroundable(project, name, true) {
override fun run(indicator: ProgressIndicator) {
createProgress(title).wrapWithProgress {
result.set(runInBackground(workspace, it))
}
}
override fun start(workspace: EvaluationWorkspace): EvaluationWorkspace? {
val result = FutureResult<EvaluationWorkspace?>()
val task = object : Task.Backgroundable(project, name, true) {
override fun run(indicator: ProgressIndicator) {
createProgress(title).wrapWithProgress {
result.set(runInBackground(workspace, it))
override fun onCancel() {
evaluationAbortedHandler.onCancel(this.title)
result.set(null)
}
override fun onThrowable(error: Throwable) {
evaluationAbortedHandler.onError(error, this.title)
result.set(null)
}
}
override fun onCancel() {
evaluationAbortedHandler.onCancel(this.title)
result.set(null)
}
override fun onThrowable(error: Throwable) {
evaluationAbortedHandler.onError(error, this.title)
result.set(null)
}
ProgressManager.getInstance().runProcessWithProgressAsynchronously(task, BackgroundableProcessIndicator(task))
return result.get()
}
ProgressManager.getInstance().runProcessWithProgressAsynchronously(task, BackgroundableProcessIndicator(task))
return result.get()
}
private val evaluationAbortedHandler = HeadlessEvaluationAbortHandler()
private fun createProgress(title: String) = when {
isUnderTeamCity -> TeamcityProgress(title)
else -> CommandLineProgress(title)
else -> throw IllegalStateException("Unexpected type of `$this`")
}
}
private val evaluationAbortedHandler = HeadlessEvaluationAbortHandler()
private fun createProgress(title: String) = when {
isUnderTeamCity -> TeamcityProgress(title)
else -> CommandLineProgress(title)
}

View File

@@ -2,14 +2,14 @@
package com.intellij.cce.evaluation.step
import com.intellij.cce.core.Language
import com.intellij.cce.evaluation.EvaluationStep
import com.intellij.cce.evaluation.ForegroundEvaluationStep
import com.intellij.cce.workspace.EvaluationWorkspace
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.ProjectRootManager
class CheckProjectSdkStep(private val project: Project, private val language: String) : EvaluationStep {
class CheckProjectSdkStep(private val project: Project, private val language: String) : ForegroundEvaluationStep {
override val name: String = "Check Project SDK"
override val description: String = "Checks that project SDK was configured properly"

View File

@@ -5,12 +5,11 @@ import com.intellij.cce.evaluation.TwoWorkspaceHandler
import com.intellij.cce.util.Progress
import com.intellij.cce.workspace.Config
import com.intellij.cce.workspace.EvaluationWorkspace
import com.intellij.openapi.project.Project
abstract class CreateWorkspaceStep(
private val config: Config,
private val handler: TwoWorkspaceHandler,
project: Project) : BackgroundEvaluationStep(project) {
private val handler: TwoWorkspaceHandler
) : BackgroundEvaluationStep {
override fun runInBackground(workspace: EvaluationWorkspace, progress: Progress): EvaluationWorkspace {
val newWorkspace = EvaluationWorkspace.create(config, SetupStatsCollectorStep.statsCollectorLogsDirectory)

View File

@@ -0,0 +1,20 @@
package com.intellij.cce.evaluation.step
import com.intellij.cce.actions.DatasetContext
import com.intellij.cce.actions.EvaluationDataset
import com.intellij.cce.util.Progress
import com.intellij.cce.workspace.EvaluationWorkspace
class DatasetPreparationStep(
private val dataset: EvaluationDataset,
private val datasetContext: DatasetContext,
) : BackgroundEvaluationStep {
override val name: String = "Preparing dataset"
override val description: String = dataset.preparationDescription
override fun runInBackground(workspace: EvaluationWorkspace, progress: Progress): EvaluationWorkspace {
dataset.prepare(datasetContext, progress)
return workspace
}
}

View File

@@ -1,20 +1,13 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.cce.evaluation.step
import com.intellij.cce.actions.ProjectOpeningUtils
import com.intellij.cce.evaluation.FinishEvaluationStep
import com.intellij.cce.workspace.EvaluationWorkspace
import com.intellij.openapi.application.ex.ApplicationEx.FORCE_EXIT
import com.intellij.openapi.application.ex.ApplicationManagerEx
import com.intellij.openapi.project.Project
import kotlin.system.exitProcess
class HeadlessFinishEvaluationStep(private val project: Project) : FinishEvaluationStep {
class HeadlessFinishEvaluationStep : FinishEvaluationStep {
override fun start(workspace: EvaluationWorkspace, withErrors: Boolean) {
if (withErrors) {
println("Evaluation completed with errors.")
ProjectOpeningUtils.closeProject(project)
exit(exitCode = 1)
throw FinishEvaluationStep.EvaluationCompletedWithErrorsException()
} else {
print("Evaluation completed. ")
if (workspace.getReports().isEmpty()) {
@@ -24,14 +17,6 @@ class HeadlessFinishEvaluationStep(private val project: Project) : FinishEvaluat
println("Reports:")
workspace.getReports().forEach { println("${it.key}: ${it.value}") }
}
ProjectOpeningUtils.closeProject(project)
exit(exitCode = 0)
}
}
private fun exit(exitCode: Int) = try {
ApplicationManagerEx.getApplicationEx().exit(FORCE_EXIT, exitCode)
} catch (t: Throwable) {
exitProcess(exitCode)
}
}

View File

@@ -10,23 +10,29 @@ import com.intellij.cce.util.Progress
import com.intellij.cce.workspace.Config
import com.intellij.cce.workspace.EvaluationWorkspace
import com.intellij.cce.workspace.FeaturesSerializer
import com.intellij.cce.workspace.info.FileSessionsInfo
import com.intellij.openapi.project.Project
class ReorderElementsStep(config: Config, project: Project) :
class ReorderElementsStep(private val config: Config) :
CreateWorkspaceStep(
Config.buildFromConfig(config) { evaluationTitle = config.reorder.title },
ReorderElementsHandler(config.reorder.features),
project) {
ReorderElementsHandler(config.reorder.features, config.actions != null)
) {
override val name: String = "Reorder elements"
override val description: String = "Reorder elements by features values"
private class ReorderElementsHandler(private val featuresForReordering: List<String>) : TwoWorkspaceHandler {
private class ReorderElementsHandler(
private val featuresForReordering: List<String>,
private val isActionProjectDataset: Boolean
) : TwoWorkspaceHandler {
override fun invoke(workspace1: EvaluationWorkspace, workspace2: EvaluationWorkspace, indicator: Progress) {
if (featuresForReordering.isEmpty()) return
check(isActionProjectDataset) {
"Reorder is available only for action-based dataset"
}
val files = workspace1.sessionsStorage.getSessionFiles()
for ((counter, file) in files.withIndex()) {
indicator.setProgress(file.first, file.first, counter.toDouble() / files.size)
@@ -54,7 +60,7 @@ class ReorderElementsStep(config: Config, project: Project) :
workspace2.featuresStorage.saveSession(newSession, fileSessionsInfo.filePath)
}
workspace2.sessionsStorage.saveSessions(
FileSessionsInfo(fileSessionsInfo.projectName, fileSessionsInfo.filePath, fileSessionsInfo.text, resultSessions)
fileSessionsInfo.copy(sessions = resultSessions)
)
}
workspace2.sessionsStorage.saveMetadata()

View File

@@ -18,16 +18,15 @@ import com.intellij.cce.workspace.info.FileSessionsInfo
import com.intellij.cce.workspace.storages.FileErrorsStorage
import com.intellij.cce.workspace.storages.SessionsStorage
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.diagnostic.Logger
import java.nio.file.Path
class ReportGenerationStep<T : EvaluationStrategy>(
private val inputWorkspaces: List<EvaluationWorkspace>?,
filters: List<SessionsFilter>,
comparisonFilters: List<CompareSessionsFilter>,
project: Project,
private val feature: EvaluableFeature<T>
) : BackgroundEvaluationStep(project) {
) : BackgroundEvaluationStep {
override val name: String = "Report generation"
override val description: String = "Generation of HTML-report"
@@ -134,8 +133,8 @@ class ReportGenerationStep<T : EvaluationStrategy>(
}
if (sessionsInfo == null) throw IllegalStateException("Sessions file doesn't exist")
for (file in sessionFile.value) {
val sessionsEvaluation = FileSessionsInfo(
sessionsInfo.projectName, sessionsInfo.filePath, sessionsInfo.text, comparisonStorage.get(file.evaluationType)
val sessionsEvaluation = sessionsInfo.copy(
sessions = comparisonStorage.get(file.evaluationType)
)
val metricsEvaluation = title2evaluator.getValue(file.evaluationType).evaluate(
sessionsEvaluation.sessions)
@@ -155,3 +154,5 @@ class ReportGenerationStep<T : EvaluationStrategy>(
}
}
}
private val LOG = Logger.getInstance(ReportGenerationStep::class.java)

View File

@@ -0,0 +1,98 @@
package com.intellij.cce.workspace
import com.google.gson.JsonObject
import com.google.gson.JsonSerializationContext
import com.intellij.cce.evaluable.EvaluationStrategy
import com.intellij.cce.evaluable.StrategySerializer
import com.intellij.cce.filter.EvaluationFilter
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import java.lang.reflect.Type
class ConfigFactoryTest {
@Test
fun `test legacy format deserialization`() {
deserialize(
"""
{
"outputDir": "outputDir",
"strategy": {}
}
""".trimIndent()
).also {
assertNull(it.actions)
}
assertThrows<IllegalStateException> {
deserialize(
"""
{
"outputDir": "outputDir",
"strategy": {},
"language": "LANG"
}
""".trimIndent()
)
}
assertThrows<IllegalStateException> {
deserialize(
"""
{
"outputDir": "outputDir",
"strategy": {},
"projectPath": "projectPath"
}
""".trimIndent()
)
}
assertThrows<IllegalStateException> {
deserialize(
"""
{
"outputDir": "outputDir",
"strategy": {},
"projectName": "projectName"
}
""".trimIndent()
)
}
deserialize(
"""
{
"outputDir": "outputDir",
"strategy": {},
"language": "LANG",
"projectPath": "projectPath",
"projectName": "projectName",
"actions": {
"evaluationRoots": []
}
}
""".trimIndent()
).also {
assertEquals("LANG", it.actions?.language)
assertEquals("projectPath", it.actions?.projectPath)
assertEquals("projectName", it.actions?.projectName)
}
}
private fun deserialize(text: String) =
ConfigFactory.deserialize(ConfigFactory.createGson(TestStrategySerializer), text, TestStrategySerializer)
private object TestStrategy : EvaluationStrategy {
override val filters: Map<String, EvaluationFilter> = emptyMap()
}
private object TestStrategySerializer : StrategySerializer<TestStrategy> {
override fun serialize(src: TestStrategy, typeOfSrc: Type, context: JsonSerializationContext): JsonObject = JsonObject()
override fun deserialize(map: Map<String, Any>, language: String): TestStrategy = TestStrategy
}
}