Skip to content

Commit

Permalink
Fix memory leaks in tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Godin committed Nov 28, 2024
1 parent 9b106bb commit 2d4603e
Show file tree
Hide file tree
Showing 21 changed files with 247 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package org.sonarsource.kotlin.api.frontend

import com.intellij.openapi.util.Disposer
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiErrorElement
import com.intellij.psi.PsiFile
Expand All @@ -33,7 +34,12 @@ import org.sonarsource.analyzer.commons.recognizers.LanguageFootprint

object KotlinCodeVerifier {

private val environment = Environment(emptyList(), LanguageVersion.LATEST_STABLE)
/**
* TODO get rid of this static (introduced by https://github.com/SonarSource/sonar-kotlin/commit/4a9410053ca901fd73a0074770b9469eb3b14aa2):
* for example maybe [org.jetbrains.kotlin.psi.KtPsiFactory.contextual] can be used instead?
*/
private val disposable = Disposer.newDisposable()
private val environment = Environment(disposable, emptyList(), LanguageVersion.LATEST_STABLE)
private val codeRecognizer = CodeRecognizer(0.9, KotlinFootprint)

private val KDOC_TAGS = listOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,18 @@ import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory
import java.io.File

/**
* @param disposable
* manages objects requiring cleanup,
* can be created using [Disposer.newDisposable] and cleanup must be done by [Disposer.dispose].
* See [documentation](https://jetbrains.org/intellij/sdk/docs/basics/disposers.html) for more details.
*/
class Environment(
val disposable: Disposable,
val classpath: List<String>,
kotlinLanguageVersion: LanguageVersion,
javaLanguageVersion: JvmTarget = JvmTarget.JVM_1_8,
) {
val disposable = Disposer.newDisposable()
val configuration = compilerConfiguration(classpath, kotlinLanguageVersion, javaLanguageVersion)
val env = kotlinCoreEnvironment(configuration, disposable)
val ktPsiFactory: KtPsiFactory = KtPsiFactory(env.project, false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package org.sonarsource.kotlin.api.sensors

import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.config.LanguageVersion
Expand Down Expand Up @@ -59,7 +60,8 @@ abstract class AbstractKotlinSensorExecuteContext(
}

val environment: Environment by lazy {
environment(sensorContext, logger)
/** [analyzeFiles] */
environment(Disposer.newDisposable(), sensorContext, logger)
}

val kotlinFiles: List<KotlinSyntaxStructure> by lazy {
Expand Down Expand Up @@ -146,8 +148,8 @@ abstract class AbstractKotlinSensorExecuteContext(
}
}


fun environment(sensorContext: SensorContext, logger: Logger): Environment = Environment(
fun environment(disposer: Disposable, sensorContext: SensorContext, logger: Logger) = Environment(
disposer,
sensorContext.config().getStringArray(SONAR_JAVA_BINARIES).toList() +
sensorContext.config().getStringArray(SONAR_JAVA_LIBRARIES).toList(),
determineKotlinLanguageVersion(sensorContext, logger),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package org.sonarsource.kotlin.ast

import com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.config.LanguageVersion
import org.sonarsource.kotlin.api.frontend.Environment
import org.sonarsource.kotlin.tools.AstPrinter
Expand All @@ -31,18 +32,25 @@ fun main(vararg args: String) {

val mode = args[0].lowercase()
val inputFile = resolveDir(args[1])
val environment = Environment(emptyList(), LanguageVersion.LATEST_STABLE)

val ktFile by lazy { environment.ktPsiFactory.createFile(inputFile.readText()) }

when (mode) {
"dot" ->
if (args.size > 2) AstPrinter.dotPrint(ktFile, resolveDir(args[2]))
else println(AstPrinter.dotPrint(ktFile))
"txt" ->
if (args.size > 2) AstPrinter.txtPrint(ktFile, resolveDir(args[2]), ktFile.viewProvider.document)
else println(AstPrinter.txtPrint(ktFile, ktFile.viewProvider.document))
else -> exitWithUsageInfoAndError()
val disposable = Disposer.newDisposable()
try {
val environment = Environment(disposable, emptyList(), LanguageVersion.LATEST_STABLE)

val ktFile by lazy { environment.ktPsiFactory.createFile(inputFile.readText()) }

when (mode) {
"dot" ->
if (args.size > 2) AstPrinter.dotPrint(ktFile, resolveDir(args[2]))
else println(AstPrinter.dotPrint(ktFile))

"txt" ->
if (args.size > 2) AstPrinter.txtPrint(ktFile, resolveDir(args[2]), ktFile.viewProvider.document)
else println(AstPrinter.txtPrint(ktFile, ktFile.viewProvider.document))

else -> exitWithUsageInfoAndError()
}
} finally {
Disposer.dispose(disposable)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
*/
package org.sonarsource.kotlin.api.checks

import com.intellij.openapi.util.Disposer
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.ObjectAssert
import com.intellij.psi.PsiElement
import com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl
import io.mockk.impl.platform.Disposable
import org.jetbrains.kotlin.config.LanguageVersion
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.js.descriptorUtils.getKotlinTypeFqName
Expand Down Expand Up @@ -47,6 +49,7 @@ import org.jetbrains.kotlin.psi.psiUtil.allChildren
import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import org.sonar.api.SonarEdition
Expand All @@ -62,7 +65,7 @@ import java.nio.charset.StandardCharsets
import java.nio.file.Path
import java.util.TreeMap

internal class ApiExtensionsKtTest {
private class ApiExtensionsKtTest : AbstractApiExtensionsKtTest() {

@Test
fun `test isLocalVariable`() {
Expand Down Expand Up @@ -283,7 +286,7 @@ internal class ApiExtensionsKtTest {
}
}

class ApiExtensionsKtDetermineTypeTest {
private class ApiExtensionsKtDetermineTypeTest : AbstractApiExtensionsKtTest() {
private val bindingContext: BindingContext
private val ktFile: KtFile

Expand Down Expand Up @@ -509,7 +512,7 @@ class ApiExtensionsKtDetermineTypeTest {
}
}

class ApiExtensionsKtDetermineSignatureTest {
private class ApiExtensionsKtDetermineSignatureTest : AbstractApiExtensionsKtTest() {
private val bindingContext: BindingContext
private val ktFile: KtFile

Expand Down Expand Up @@ -544,9 +547,8 @@ class ApiExtensionsKtDetermineSignatureTest {
}
}

class ApiExtensionsScopeFunctionResolutionTest {
companion object {
private fun generateAst(funContent: String) = """
private class ApiExtensionsScopeFunctionResolutionTest : AbstractApiExtensionsKtTest() {
private fun generateAst(funContent: String) = """
package bar
class Foo {
Expand All @@ -557,7 +559,6 @@ class ApiExtensionsScopeFunctionResolutionTest {
}
}
""".let { parse(it) }.let { it.psiFile to it.bindingContext }
}

@Test
fun `resolve this as arg in with`() {
Expand Down Expand Up @@ -698,28 +699,40 @@ class ApiExtensionsScopeFunctionResolutionTest {
}
}

private fun parse(code: String) = kotlinTreeOf(
code,
Environment(
listOf("build/classes/kotlin/main") + System.getProperty("java.class.path").split(File.pathSeparatorChar),
LanguageVersion.LATEST_STABLE
),
TestInputFileBuilder("moduleKey", "src/org/foo/kotlin.kt")
.setCharset(StandardCharsets.UTF_8)
.initMetadata(code)
.build()
)

private fun parseWithoutParsingExceptions(code: String): KtFile {
val environment = Environment(
listOf("build/classes/kotlin/main") + System.getProperty("java.class.path").split(File.pathSeparatorChar),
LanguageVersion.LATEST_STABLE
private abstract class AbstractApiExtensionsKtTest {
private val disposable = Disposer.newDisposable()

@AfterEach
fun dispose() {
Disposer.dispose(disposable)
}

fun parse(code: String) = kotlinTreeOf(
code,
Environment(
disposable,
listOf("build/classes/kotlin/main") + System.getProperty("java.class.path").split(File.pathSeparatorChar),
LanguageVersion.LATEST_STABLE
),
TestInputFileBuilder("moduleKey", "src/org/foo/kotlin.kt")
.setCharset(StandardCharsets.UTF_8)
.initMetadata(code)
.build()
)
val inputFile = TestInputFileBuilder("moduleKey", "src/org/foo/kotlin.kt")
.setCharset(StandardCharsets.UTF_8)
.initMetadata(code)
.build()
return environment.ktPsiFactory.createFile(inputFile.uri().path, code.replace("""\r\n?""".toRegex(), "\n"))

fun parseWithoutParsingExceptions(code: String): KtFile {
val environment = Environment(
disposable,
listOf("build/classes/kotlin/main") + System.getProperty("java.class.path").split(File.pathSeparatorChar),
LanguageVersion.LATEST_STABLE
)
val inputFile = TestInputFileBuilder("moduleKey", "src/org/foo/kotlin.kt")
.setCharset(StandardCharsets.UTF_8)
.initMetadata(code)
.build()
return environment.ktPsiFactory.createFile(inputFile.uri().path, code.replace("""\r\n?""".toRegex(), "\n"))
}

}

private class KtExpressionAssert(expression: KtExpression?) : ObjectAssert<KtExpression>(expression) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
*/
package org.sonarsource.kotlin.api.checks

import com.intellij.openapi.util.Disposer
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.kotlin.config.LanguageVersion
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import org.sonar.api.batch.fs.internal.DefaultInputFile
import org.sonar.api.batch.fs.internal.TestInputFileBuilder
Expand All @@ -31,8 +33,14 @@ import java.nio.file.Path
import java.nio.file.Paths

class FieldMatcherTest {
private val disposable = Disposer.newDisposable()

val environment = Environment(listOf("../kotlin-checks-test-sources/build/classes/kotlin/main"), LanguageVersion.LATEST_STABLE)
@AfterEach
fun dispose() {
Disposer.dispose(disposable)
}

val environment = Environment(disposable, listOf("../kotlin-checks-test-sources/build/classes/kotlin/main"), LanguageVersion.LATEST_STABLE)

val path: Path = Paths.get("../kotlin-checks-test-sources/src/main/kotlin/sample/fields.kt")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package org.sonarsource.kotlin.api.checks

import com.intellij.openapi.util.Disposer
import io.mockk.Called
import io.mockk.spyk
import io.mockk.unmockkAll
Expand All @@ -28,6 +29,7 @@ import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.util.getCall
import org.jetbrains.kotlin.types.typeUtil.TypeNullability
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import org.sonar.api.batch.fs.internal.TestInputFileBuilder
import org.sonarsource.kotlin.api.frontend.Environment
Expand All @@ -37,8 +39,14 @@ import java.nio.file.Files
import java.nio.file.Paths

class FunMatcherTest {
private val disposable = Disposer.newDisposable()

val environment = Environment(listOf("../kotlin-checks-test-sources/build/classes/kotlin/main"), LanguageVersion.LATEST_STABLE)
@AfterEach
fun dispose() {
Disposer.dispose(disposable)
}

val environment = Environment(disposable, listOf("../kotlin-checks-test-sources/build/classes/kotlin/main"), LanguageVersion.LATEST_STABLE)
val path = Paths.get("../kotlin-checks-test-sources/src/main/kotlin/sample/functions.kt")
val content = String(Files.readAllBytes(path))
val inputFile = TestInputFileBuilder("moduleKey", "src/org/foo/kotlin.kt")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@
*/
package org.sonarsource.kotlin.api.frontend

import com.intellij.openapi.util.Disposer
import org.assertj.core.api.Assertions
import org.jetbrains.kotlin.config.LanguageVersion
import org.jetbrains.kotlin.psi.KtFile
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import org.sonarsource.kotlin.tools.AstPrinter
import java.nio.charset.StandardCharsets
Expand All @@ -27,18 +29,30 @@ import java.nio.file.Path
import java.util.stream.Collectors
import kotlin.io.path.readText

private val environment = Environment(emptyList(), LanguageVersion.LATEST_STABLE)

fun main() {
fix_all_cls_files_test_automatically()
val disposable = Disposer.newDisposable()
try {
fix_all_cls_files_test_automatically(Environment(disposable, emptyList(), LanguageVersion.LATEST_STABLE))
} finally {
Disposer.dispose(disposable)
}
}

internal class KotlinASTTest {
private val disposable = Disposer.newDisposable()

@AfterEach
fun dispose() {
Disposer.dispose(disposable)
}

private val environment = Environment(disposable, emptyList(), LanguageVersion.LATEST_STABLE)

@Test
fun all_kotlin_files() {
for (kotlinPath in kotlinSources()) {
val astPath = Path.of(kotlinPath.toString().replaceFirst("\\.kts?$".toRegex(), ".txt"))
val ktFile = parse(kotlinPath)
val ktFile = parse(environment, kotlinPath)
val actualAst = AstPrinter.txtPrint(ktFile, ktFile.viewProvider.document)
val expectingAst = if (astPath.toFile().exists()) astPath.readText() else ""
Assertions.assertThat(actualAst.trim { it <= ' ' })
Expand All @@ -48,10 +62,10 @@ internal class KotlinASTTest {
}
}

private fun fix_all_cls_files_test_automatically() {
private fun fix_all_cls_files_test_automatically(environment: Environment) {
for (kotlinPath in kotlinSources()) {
val astPath = Path.of(kotlinPath.toString().replaceFirst("\\.kts?$".toRegex(), ".txt"))
val actualAst = AstPrinter.txtPrint(parse(kotlinPath))
val actualAst = AstPrinter.txtPrint(parse(environment, kotlinPath))
Files.write(astPath, actualAst.toByteArray(StandardCharsets.UTF_8))
}
}
Expand All @@ -68,7 +82,7 @@ private fun kotlinSources(): List<Path> {
}
}

private fun parse(path: Path): KtFile {
private fun parse(environment: Environment, path: Path): KtFile {
val code = String(Files.readAllBytes(path), StandardCharsets.UTF_8)
return try {
environment.ktPsiFactory.createFile(code.replace("""\r\n?""".toRegex(), "\n"))
Expand Down
Loading

0 comments on commit 2d4603e

Please sign in to comment.