<commit_before>/*
 * Use of this source code is governed by the MIT license that can be
 * found in the LICENSE file.
 */

package org.rust.cargo.runconfig.test

import com.google.gson.Gson
import com.google.gson.JsonObject
import com.intellij.execution.testframework.TestConsoleProperties
import com.intellij.execution.testframework.sm.ServiceMessageBuilder
import com.intellij.execution.testframework.sm.runner.OutputToGeneralTestEventsConverter
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.text.StringUtil.unescapeStringCharacters
import com.intellij.openapi.util.text.StringUtil.unquoteString
import com.intellij.util.execution.ParametersListUtil
import jetbrains.buildServer.messages.serviceMessages.ServiceMessageVisitor
import org.rust.cargo.project.model.cargoProjects
import org.rust.cargo.project.model.impl.allPackages
import org.rust.cargo.project.settings.toolchain
import org.rust.cargo.project.workspace.CargoWorkspace
import org.rust.cargo.project.workspace.PackageOrigin
import org.rust.cargo.runconfig.test.CargoTestEventsConverter.State.*
import org.rust.openapiext.JsonUtils.tryParseJsonObject
import org.rust.stdext.removeLast
import java.io.File

private typealias NodeId = String

class CargoTestEventsConverter(
    testFrameworkName: String,
    consoleProperties: TestConsoleProperties
) : OutputToGeneralTestEventsConverter(testFrameworkName, consoleProperties) {
    private val project: Project = consoleProperties.project
    private var converterState: State = START_MESSAGE

    private val suitesStack: MutableList<NodeId> = mutableListOf()
    private val target: NodeId get() = suitesStack.firstOrNull() ?: ROOT_SUITE
    private val currentSuite: NodeId get() = suitesStack.lastOrNull() ?: ROOT_SUITE

    private val suitesToNotFinishedChildren: MutableMap<NodeId, MutableSet<NodeId>> = hashMapOf()
    private val testsStartTimes: MutableMap<NodeId, Long> = hashMapOf()
    private val pendingFinishedSuites: MutableSet<NodeId> = linkedSetOf()

    private var doctestPackageCounter: Int = 0

    override fun processServiceMessages(text: String, outputType: Key<*>, visitor: ServiceMessageVisitor): Boolean {
        handleStartMessage(text)

        val escapedText = text.replace(NAME_RE) { it.value.replace("\\", "\\\\") }
        val jsonObject = tryParseJsonObject(escapedText)

        return when {
            jsonObject == null -> false
            handleTestMessage(jsonObject, outputType, visitor) -> true
            handleSuiteMessage(jsonObject, outputType, visitor) -> true
            else -> true // don't print unknown json messages
        }
    }

    private fun handleStartMessage(text: String) {
        when (converterState) {
            START_MESSAGE -> {
                val clean = text.trim().lowercase()
                converterState = when {
                    clean == "running" -> EXECUTABLE_NAME
                    clean == "doc-tests" -> DOCTESTS_PACKAGE_NAME
                    TARGET_PATH_PART in text -> {
                        val executableName = ParametersListUtil.parse(text)
                            .firstOrNull()
                            ?.substringAfterLast("/")
                            ?.substringBeforeLast(".")
                            ?.takeIf { it.isNotEmpty() }
                            ?: error("Can't parse the executable name")
                        suitesStack.add(executableName)
                        START_MESSAGE
                    }
                    else -> return
                }
            }
            EXECUTABLE_NAME -> {
                val executableName = text
                    .trim()
                    .substringAfterLast(project.toolchain?.fileSeparator ?: File.separator)
                    .substringBeforeLast(".")
                    .takeIf { it.isNotEmpty() }
                    ?: error("Can't parse the executable name")
                suitesStack.add(executableName)
                converterState = START_MESSAGE
            }
            DOCTESTS_PACKAGE_NAME -> {
                // package-name -> target_name-#idoctests
                // `rustdoc` produces package name instead of target name, but since only library targets can have
                // doctests we can find the corresponding lib target
                val packageName = text.trim()
                val libTargetName = findLibTargetForPackageName(project, packageName)?.normName
                val targetNameWithSuffix = "${libTargetName ?: packageName}-${doctestPackageCounter++}$DOCTESTS_SUFFIX"
                suitesStack.add(targetNameWithSuffix)
                converterState = START_MESSAGE
            }
        }
    }

    private fun handleTestMessage(
        jsonObject: JsonObject,
        outputType: Key<*>,
        visitor: ServiceMessageVisitor<commit_msg>RUN: Fix parsing of executable path in Test Tool Window<commit_after>/*
 * Use of this source code is governed by the MIT license that can be
 * found in the LICENSE file.
 */

package org.rust.cargo.runconfig.test

import com.google.gson.Gson
import com.google.gson.JsonObject
import com.intellij.execution.testframework.TestConsoleProperties
import com.intellij.execution.testframework.sm.ServiceMessageBuilder
import com.intellij.execution.testframework.sm.runner.OutputToGeneralTestEventsConverter
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.text.StringUtil.unescapeStringCharacters
import com.intellij.openapi.util.text.StringUtil.unquoteString
import com.intellij.util.execution.ParametersListUtil
import jetbrains.buildServer.messages.serviceMessages.ServiceMessageVisitor
import org.rust.cargo.project.model.cargoProjects
import org.rust.cargo.project.model.impl.allPackages
import org.rust.cargo.project.settings.toolchain
import org.rust.cargo.project.workspace.CargoWorkspace
import org.rust.cargo.project.workspace.PackageOrigin
import org.rust.cargo.runconfig.test.CargoTestEventsConverter.State.*
import org.rust.openapiext.JsonUtils.tryParseJsonObject
import org.rust.stdext.removeLast
import java.io.File

private typealias NodeId = String

class CargoTestEventsConverter(
    testFrameworkName: String,
    consoleProperties: TestConsoleProperties
) : OutputToGeneralTestEventsConverter(testFrameworkName, consoleProperties) {
    private val project: Project = consoleProperties.project
    private var converterState: State = START_MESSAGE

    private val suitesStack: MutableList<NodeId> = mutableListOf()
    private val target: NodeId get() = suitesStack.firstOrNull() ?: ROOT_SUITE
    private val currentSuite: NodeId get() = suitesStack.lastOrNull() ?: ROOT_SUITE

    private val suitesToNotFinishedChildren: MutableMap<NodeId, MutableSet<NodeId>> = hashMapOf()
    private val testsStartTimes: MutableMap<NodeId, Long> = hashMapOf()
    private val pendingFinishedSuites: MutableSet<NodeId> = linkedSetOf()

    private var doctestPackageCounter: Int = 0

    override fun processServiceMessages(text: String, outputType: Key<*>, visitor: ServiceMessageVisitor): Boolean {
        handleStartMessage(text)

        val escapedText = text.replace(NAME_RE) { it.value.replace("\\", "\\\\") }
        val jsonObject = tryParseJsonObject(escapedText)

        return when {
            jsonObject == null -> false
            handleTestMessage(jsonObject, outputType, visitor) -> true
            handleSuiteMessage(jsonObject, outputType, visitor) -> true
            else -> true // don't print unknown json messages
        }
    }

    private fun handleStartMessage(text: String) {
        when (converterState) {
            START_MESSAGE -> {
                val clean = text.trim().lowercase()
                converterState = when {
                    clean == "running" -> EXECUTABLE_NAME
                    clean == "doc-tests" -> DOCTESTS_PACKAGE_NAME
                    TARGET_PATH_PART in text -> {
                        val executableName = ParametersListUtil.parse(text)
                            .firstOrNull()
                            ?.substringAfterLast("/")
                            ?.substringBeforeLast(".")
                            ?.takeIf { it.isNotEmpty() }
                            ?: error("Can't parse the executable name")
                        suitesStack.add(executableName)
                        START_MESSAGE
                    }
                    else -> return
                }
            }
            EXECUTABLE_NAME -> {
                val executableName = text
                    .trim()
                    .substringAfterLast(project.toolchain?.fileSeparator ?: File.separator)
                    .substringBeforeLast(".")
                    .removeSuffix(")")
                    .takeIf { it.isNotEmpty() }
                    ?: error("Can't parse the executable name")
                suitesStack.add(executableName)
                converterState = START_MESSAGE
            }
            DOCTESTS_PACKAGE_NAME -> {
                // package-name -> target_name-#idoctests
                // `rustdoc` produces package name instead of target name, but since only library targets can have
                // doctests we can find the corresponding lib target
                val packageName = text.trim()
                val libTargetName = findLibTargetForPackageName(project, packageName)?.normName
                val targetNameWithSuffix = "${libTargetName ?: packageName}-${doctestPackageCounter++}$DOCTESTS_SUFFIX"
                suitesStack.add(targetNameWithSuffix)
                converterState = START_MESSAGE
            }
        }
    }

    private fun handleTestMessage(
        jsonObject: JsonObject,
        outputType: Key<*>,
        visitor: ServiceMessageVisitor
{'fraction_non_alphanumeric': 0.0836637186133984, 'fraction_numerical': 0.0002033140185015757, 'mean_word_length': 2.435055865921788, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 41, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3874625', 'n_tokens_mistral': 2621, 'n_tokens_neox': 2487, 'n_words': 543}