From 162f677c8ed4ab6c7f1e7a1ebfa4a731aeb780b6 Mon Sep 17 00:00:00 2001 From: Max Medvedev Date: Tue, 11 Nov 2025 11:23:34 +0100 Subject: [PATCH] [junit5] ability to specify log level for testing followup for IJ-CR-181003 GitOrigin-RevId: a736aa6b750ebd0061117c2d183ac66ffe286360 --- .../psi/impl/file/impl/FileContextTest.kt | 8 +- .../psi/impl/file/impl/FileMoveTest.kt | 13 +-- .../impl/FileMoveWithWaitForIndexingTest.kt | 13 +-- .../junit5/src/EnableTracingFor.kt | 26 ++++++ platform/testFramework/junit5/src/LogLevel.kt | 27 ------- .../junit5/src/impl/EnableTracingExtension.kt | 54 +++++++++++++ .../junit5/src/impl/LogLevelExtension.kt | 79 ------------------- .../showcase/JUnit5EnableTracingInTest.kt | 29 +++++++ .../intellij/openapi/diagnostic/Logger.java | 16 +--- 9 files changed, 124 insertions(+), 141 deletions(-) create mode 100644 platform/testFramework/junit5/src/EnableTracingFor.kt delete mode 100644 platform/testFramework/junit5/src/LogLevel.kt create mode 100644 platform/testFramework/junit5/src/impl/EnableTracingExtension.kt delete mode 100644 platform/testFramework/junit5/src/impl/LogLevelExtension.kt create mode 100644 platform/testFramework/junit5/test/showcase/JUnit5EnableTracingInTest.kt diff --git a/platform/lang-impl/testSources/com/intellij/psi/impl/file/impl/FileContextTest.kt b/platform/lang-impl/testSources/com/intellij/psi/impl/file/impl/FileContextTest.kt index 3891197669b0..98ed93bbb3a2 100644 --- a/platform/lang-impl/testSources/com/intellij/psi/impl/file/impl/FileContextTest.kt +++ b/platform/lang-impl/testSources/com/intellij/psi/impl/file/impl/FileContextTest.kt @@ -5,7 +5,6 @@ import com.intellij.codeInsight.multiverse.* import com.intellij.openapi.application.edtWriteAction import com.intellij.openapi.application.readAction import com.intellij.openapi.application.writeAction -import com.intellij.openapi.diagnostic.LogLevel import com.intellij.openapi.module.Module import com.intellij.openapi.project.rootManager import com.intellij.platform.testFramework.junit5.projectStructure.fixture.withSharedSourceEnabled @@ -13,7 +12,7 @@ import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiFile import com.intellij.psi.impl.PsiManagerEx import com.intellij.testFramework.common.timeoutRunBlocking -import com.intellij.testFramework.junit5.LogLevelWithClass +import com.intellij.testFramework.junit5.EnableTracingFor import com.intellij.testFramework.junit5.TestApplication import com.intellij.testFramework.junit5.fixture.* import kotlinx.coroutines.ensureActive @@ -24,9 +23,8 @@ import org.junit.jupiter.api.Test import kotlin.time.Duration.Companion.seconds @TestApplication -@LogLevelWithClass( - category = CodeInsightContextManagerImpl::class, - level = LogLevel.TRACE +@EnableTracingFor( + categoryClasses = [CodeInsightContextManagerImpl::class], ) internal class FileContextTest { companion object { diff --git a/platform/lang-impl/testSources/com/intellij/psi/impl/file/impl/FileMoveTest.kt b/platform/lang-impl/testSources/com/intellij/psi/impl/file/impl/FileMoveTest.kt index c950a7972434..65075bb64280 100644 --- a/platform/lang-impl/testSources/com/intellij/psi/impl/file/impl/FileMoveTest.kt +++ b/platform/lang-impl/testSources/com/intellij/psi/impl/file/impl/FileMoveTest.kt @@ -4,7 +4,6 @@ package com.intellij.psi.impl.file.impl import com.intellij.codeInsight.multiverse.* import com.intellij.openapi.application.readAction import com.intellij.openapi.application.writeAction -import com.intellij.openapi.diagnostic.LogLevel import com.intellij.openapi.diagnostic.thisLogger import com.intellij.openapi.module.Module import com.intellij.openapi.module.ModuleManager @@ -17,7 +16,7 @@ import com.intellij.psi.PsiManager import com.intellij.psi.impl.PsiManagerImpl import com.intellij.psi.util.PsiUtilCore.ensureValid import com.intellij.testFramework.common.timeoutRunBlocking -import com.intellij.testFramework.junit5.LogLevelWithClass +import com.intellij.testFramework.junit5.EnableTracingFor import com.intellij.testFramework.junit5.TestApplication import com.intellij.testFramework.junit5.fixture.fileOrDirInProjectFixture import com.intellij.testFramework.junit5.fixture.moduleInProjectFixture @@ -27,13 +26,9 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.fail -@LogLevelWithClass( - category = CodeInsightContextManagerImpl::class, - level = LogLevel.TRACE -) -@com.intellij.testFramework.junit5.LogLevel( - category = "#com.intellij.psi.impl.file.impl.MultiverseFileViewProviderCache", - level = LogLevel.TRACE +@EnableTracingFor( + categories = ["#com.intellij.psi.impl.file.impl.MultiverseFileViewProviderCache"], + categoryClasses = [CodeInsightContextManagerImpl::class] ) @TestApplication internal class FileMoveTest { diff --git a/platform/lang-impl/testSources/com/intellij/psi/impl/file/impl/FileMoveWithWaitForIndexingTest.kt b/platform/lang-impl/testSources/com/intellij/psi/impl/file/impl/FileMoveWithWaitForIndexingTest.kt index a6d02f043e87..5624f1664636 100644 --- a/platform/lang-impl/testSources/com/intellij/psi/impl/file/impl/FileMoveWithWaitForIndexingTest.kt +++ b/platform/lang-impl/testSources/com/intellij/psi/impl/file/impl/FileMoveWithWaitForIndexingTest.kt @@ -4,7 +4,6 @@ package com.intellij.psi.impl.file.impl import com.intellij.codeInsight.multiverse.* import com.intellij.openapi.application.readAction import com.intellij.openapi.application.writeAction -import com.intellij.openapi.diagnostic.LogLevel import com.intellij.openapi.diagnostic.thisLogger import com.intellij.openapi.module.Module import com.intellij.openapi.module.ModuleManager @@ -18,7 +17,7 @@ import com.intellij.psi.impl.PsiManagerImpl import com.intellij.psi.util.PsiUtilCore.ensureValid import com.intellij.testFramework.IndexingTestUtil import com.intellij.testFramework.common.timeoutRunBlocking -import com.intellij.testFramework.junit5.LogLevelWithClass +import com.intellij.testFramework.junit5.EnableTracingFor import com.intellij.testFramework.junit5.TestApplication import com.intellij.testFramework.junit5.fixture.fileOrDirInProjectFixture import com.intellij.testFramework.junit5.fixture.moduleInProjectFixture @@ -28,13 +27,9 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.fail -@LogLevelWithClass( - category = CodeInsightContextManagerImpl::class, - level = LogLevel.TRACE -) -@com.intellij.testFramework.junit5.LogLevel( - category = "#com.intellij.psi.impl.file.impl.MultiverseFileViewProviderCache", - level = LogLevel.TRACE +@EnableTracingFor( + categories = ["#com.intellij.psi.impl.file.impl.MultiverseFileViewProviderCache"], + categoryClasses = [CodeInsightContextManagerImpl::class], ) @TestApplication internal class FileMoveWithWaitForIndexingTest { diff --git a/platform/testFramework/junit5/src/EnableTracingFor.kt b/platform/testFramework/junit5/src/EnableTracingFor.kt new file mode 100644 index 000000000000..4492e64aa778 --- /dev/null +++ b/platform/testFramework/junit5/src/EnableTracingFor.kt @@ -0,0 +1,26 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.testFramework.junit5 + +import com.intellij.testFramework.junit5.impl.EnableTracingExtension +import org.jetbrains.annotations.TestOnly +import org.junit.jupiter.api.extension.ExtendWith +import kotlin.reflect.KClass + +/** + * Enables tracing of the specified logging categories inside the annotated test class or method. + * The corresponding messages will be present in the log file on disk. + * Also, they will be visible in which is printed to str-error if the test fails. + * + * After the annotated test/function finishes, the logger will be restored to DEBUG level. + * + * For convenience, you can specify logging categories by their fully qualified names and/or by their classes. + * If both categories and categoryClasses are specified, all of them will be enabled. + */ +@TestOnly +@Repeatable +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) +@ExtendWith(EnableTracingExtension::class) +annotation class EnableTracingFor( + val categories: Array = [], + val categoryClasses: Array> = [], +) \ No newline at end of file diff --git a/platform/testFramework/junit5/src/LogLevel.kt b/platform/testFramework/junit5/src/LogLevel.kt deleted file mode 100644 index e951bf34af24..000000000000 --- a/platform/testFramework/junit5/src/LogLevel.kt +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.testFramework.junit5 - -import com.intellij.openapi.diagnostic.LogLevel -import com.intellij.testFramework.junit5.impl.LogLevelExtension -import com.intellij.testFramework.junit5.impl.LogLevelWithClassExtension -import org.jetbrains.annotations.TestOnly -import org.junit.jupiter.api.extension.ExtendWith -import kotlin.reflect.KClass - -@TestOnly -@Repeatable -@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) -@ExtendWith(LogLevelExtension::class) -annotation class LogLevel( - val category: String, - val level: LogLevel, -) - -@TestOnly -@Repeatable -@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) -@ExtendWith(LogLevelWithClassExtension::class) -annotation class LogLevelWithClass( - val category: KClass<*>, - val level: LogLevel, -) \ No newline at end of file diff --git a/platform/testFramework/junit5/src/impl/EnableTracingExtension.kt b/platform/testFramework/junit5/src/impl/EnableTracingExtension.kt new file mode 100644 index 000000000000..de885a20f87b --- /dev/null +++ b/platform/testFramework/junit5/src/impl/EnableTracingExtension.kt @@ -0,0 +1,54 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.testFramework.junit5.impl + +import com.intellij.openapi.diagnostic.LogLevel +import com.intellij.openapi.diagnostic.Logger +import com.intellij.testFramework.junit5.EnableTracingFor +import org.jetbrains.annotations.TestOnly +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.InvocationInterceptor +import org.junit.platform.commons.support.AnnotationSupport + +@TestOnly +internal class EnableTracingExtension : AbstractInvocationInterceptor() { + override fun intercept(invocation: InvocationInterceptor.Invocation, context: ExtensionContext): T { + val annotations = collectAnnotations(context).ifEmpty { + return invocation.proceed() + } + + val loggers = annotations.flatMap { annotation -> + annotation.categories.map { Logger.getInstance(it) } + + annotation.categoryClasses.map { Logger.getInstance(it.java) } + } + + return doWithTracingEnabled(loggers, invocation) + } + + private fun doWithTracingEnabled( + infos: List, + invocation: InvocationInterceptor.Invocation, + ): T { + val valuesBefore = infos.map { info -> enableTracing(info) } + + try { + return invocation.proceed() + } + finally { + valuesBefore.asReversed().forEach { it() } + } + } + + private fun enableTracing(logger: Logger): () -> Unit { + logger.setLevel(LogLevel.TRACE) + return { + logger.setLevel(LogLevel.INFO) + } + } + + private inline fun collectAnnotations(context: ExtensionContext): List { + val annotations = + AnnotationSupport.findRepeatableAnnotations(context.testClass, C::class.java) + + AnnotationSupport.findRepeatableAnnotations(context.element, C::class.java) + return annotations.distinct() + } +} \ No newline at end of file diff --git a/platform/testFramework/junit5/src/impl/LogLevelExtension.kt b/platform/testFramework/junit5/src/impl/LogLevelExtension.kt deleted file mode 100644 index f55b34b30639..000000000000 --- a/platform/testFramework/junit5/src/impl/LogLevelExtension.kt +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.testFramework.junit5.impl - -import com.intellij.openapi.diagnostic.Logger -import com.intellij.testFramework.junit5.LogLevel -import com.intellij.testFramework.junit5.LogLevelWithClass -import org.jetbrains.annotations.TestOnly -import org.junit.jupiter.api.extension.ExtensionContext -import org.junit.jupiter.api.extension.InvocationInterceptor -import org.junit.platform.commons.support.AnnotationSupport - -@TestOnly -internal class LogLevelExtension : AbstractInvocationInterceptor() { - override fun intercept(invocation: InvocationInterceptor.Invocation, context: ExtensionContext): T { - val annotations = collectAnnotations(context).ifEmpty { - return invocation.proceed() - } - - val infos = annotations.map { - LogLevelInfo(Logger.getInstance(it.category), it.level) - } - - return doWithLogLevelInstalled(infos, invocation) - } -} - -@TestOnly -internal class LogLevelWithClassExtension : AbstractInvocationInterceptor() { - override fun intercept(invocation: InvocationInterceptor.Invocation, context: ExtensionContext): T { - val annotations = collectAnnotations(context).ifEmpty { - return invocation.proceed() - } - - val infos = annotations.map { - LogLevelInfo(Logger.getInstance(it.category.java), it.level) - } - - return doWithLogLevelInstalled(infos, invocation) - } -} - -private inline fun collectAnnotations(context: ExtensionContext): List { - val annotations = - AnnotationSupport.findRepeatableAnnotations(context.testClass, C::class.java) + - AnnotationSupport.findRepeatableAnnotations(context.element, C::class.java) - return annotations.distinct() -} - -private data class LogLevelInfo( - val logger: Logger, - val level: com.intellij.openapi.diagnostic.LogLevel, -) - -private fun doWithLogLevelInstalled( - infos: List, - invocation: InvocationInterceptor.Invocation, -): T { - val valuesBefore = infos.map { info -> - setLogLevel(info) - } - try { - return invocation.proceed() - } - finally { - for (handle in valuesBefore.asReversed()) { - handle() - } - } -} - -private fun setLogLevel(info: LogLevelInfo): () -> Unit { - val (logger, logLevel) = info - - val prevLevel = logger.level - logger.level = logLevel - return { - logger.level = prevLevel - } -} diff --git a/platform/testFramework/junit5/test/showcase/JUnit5EnableTracingInTest.kt b/platform/testFramework/junit5/test/showcase/JUnit5EnableTracingInTest.kt new file mode 100644 index 000000000000..b56e65522193 --- /dev/null +++ b/platform/testFramework/junit5/test/showcase/JUnit5EnableTracingInTest.kt @@ -0,0 +1,29 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.testFramework.junit5.showcase + +import com.intellij.openapi.diagnostic.Logger +import com.intellij.testFramework.junit5.EnableTracingFor +import org.junit.jupiter.api.Test + +// MyBusinessLogic logger works with the TRACE level inside the test run. +@EnableTracingFor(categoryClasses = [MyBusinessLogic::class]) +class JUnit5EnableTracingInTest { + + @Test + fun `traces are written to log file`() { + MyBusinessLogic().run() + } +} + + +private class MyBusinessLogic { + private val LOG = Logger.getInstance(MyBusinessLogic::class.java) + + fun run() { + LOG.trace("MyBusinessLogic#run started") + + // doing stuff ... + + LOG.info("MyBusinessLogic#run finished") + } +} \ No newline at end of file diff --git a/platform/util/src/com/intellij/openapi/diagnostic/Logger.java b/platform/util/src/com/intellij/openapi/diagnostic/Logger.java index c534df9b47a7..168b90de65ac 100644 --- a/platform/util/src/com/intellij/openapi/diagnostic/Logger.java +++ b/platform/util/src/com/intellij/openapi/diagnostic/Logger.java @@ -4,7 +4,10 @@ package com.intellij.openapi.diagnostic; import com.intellij.util.ArrayUtilRt; import com.intellij.util.ExceptionUtil; import org.apache.log4j.Level; -import org.jetbrains.annotations.*; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; import java.lang.reflect.Constructor; import java.util.Collection; @@ -488,17 +491,6 @@ public abstract class Logger { error(getClass() + " should override '#setLevel(LogLevel)'"); } - @ApiStatus.Internal - public LogLevel getLevel() { - if (isTraceEnabled()) { - return LogLevel.TRACE; - } - if (isDebugEnabled()) { - return LogLevel.DEBUG; - } - return LogLevel.INFO; - } - private static final boolean ourRethrowCE = "true".equals(System.getProperty("idea.log.rethrow.ce", "true")); public static boolean shouldRethrow(@NotNull Throwable t) {