mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[python][junit5] move PyDefaultTestApplication to the junit5Tests.framework module
Merge-request: IJ-MR-176009 Merged-by: Vitaly Legchilkin <Vitaly.Legchilkin@jetbrains.com> GitOrigin-RevId: 4be35423f74375e1b37e0ea87fd6490a950060f6
This commit is contained in:
committed by
intellij-monorepo-bot
parent
464bc08113
commit
0ce97b7649
@@ -54,6 +54,11 @@ jvm_library(
|
||||
"@lib//:python-community-junit5_tests-framework-uk-webcompere-system-stubs-jupiter",
|
||||
"//platform/util/coroutines",
|
||||
"@lib//:byte-buddy",
|
||||
"//platform/testFramework",
|
||||
"//platform/testFramework:testFramework_test_lib",
|
||||
"//platform/editor-ui-api:editor-ui",
|
||||
"//platform/editor-ui-ex:editor-ex",
|
||||
"//platform/analysis-impl",
|
||||
],
|
||||
exports = [
|
||||
"//python/setup-test-environment:community-testFramework-testEnv",
|
||||
|
||||
@@ -65,5 +65,9 @@
|
||||
</orderEntry>
|
||||
<orderEntry type="module" module-name="intellij.platform.util.coroutines" scope="TEST" />
|
||||
<orderEntry type="library" scope="TEST" name="byte-buddy" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.editor.ui" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.editor.ex" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.analysis.impl" scope="TEST" />
|
||||
</component>
|
||||
</module>
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
// 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.python.junit5Tests.framework
|
||||
|
||||
import com.intellij.python.junit5Tests.framework.metaInfo.TestMetaInfoExtension.Companion.getTestClassInfo
|
||||
import com.intellij.python.junit5Tests.framework.metaInfo.TestMetaInfoExtension.Companion.setTestCaseFilePath
|
||||
import com.intellij.python.junit5Tests.framework.metaInfo.resolveTestName
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.junit.jupiter.api.TestTemplate
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.junit.jupiter.api.extension.ExtensionContext
|
||||
import org.junit.jupiter.api.extension.TestTemplateInvocationContext
|
||||
import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.stream.Stream
|
||||
import kotlin.io.path.isDirectory
|
||||
import kotlin.io.path.relativeTo
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
|
||||
/**
|
||||
* Test annotation that is used in conjunction with JUnit 5's [TestTemplate] to enable
|
||||
* parameterized testing over files within a directory. Each test invocation corresponds
|
||||
* to an individual file that matches the specified filter criteria.
|
||||
*
|
||||
* This annotation is processed by the [AllFilesInFolderTestCaseProvider] extension
|
||||
*
|
||||
* @param [fileNameFilter]: A string representing the regex pattern used to filter files
|
||||
* in the folder for test execution. The default value is `FileNameFilter.ALL_FILES`,
|
||||
* which means all files are included.
|
||||
*/
|
||||
@TestOnly
|
||||
@TestTemplate
|
||||
@ExtendWith(AllFilesInFolderTestCaseProvider::class)
|
||||
annotation class FolderTest(val fileNameFilter: String = FileNameFilter.ALL_FILES)
|
||||
|
||||
@TestOnly
|
||||
@Suppress("unused")
|
||||
class FileNameFilter {
|
||||
companion object {
|
||||
const val ALL_FILES = """^.*$"""
|
||||
const val PYTHON = """^.*\.(py)$"""
|
||||
}
|
||||
}
|
||||
|
||||
private class AllFilesInFolderTestCaseProvider : TestTemplateInvocationContextProvider {
|
||||
private fun getTestFolderPath(context: ExtensionContext): Path {
|
||||
val metaInfo = context.getTestClassInfo()
|
||||
val testName = context.resolveTestName()
|
||||
|
||||
val testResourcePath = metaInfo.getTestResourcePath(fileName = testName)
|
||||
return testResourcePath?.takeIf { it.isDirectory() }
|
||||
?: error("Please make a folder with tests for \"${context.testMethod.getOrNull()?.name}\": ${metaInfo.testDataPath}/${testName}")
|
||||
}
|
||||
|
||||
override fun supportsTestTemplate(context: ExtensionContext): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun provideTestTemplateInvocationContexts(context: ExtensionContext): Stream<TestTemplateInvocationContext?>? {
|
||||
val testFolderPath = getTestFolderPath(context)
|
||||
|
||||
val folderTest = context.testMethod?.getOrNull()?.getAnnotation(FolderTest::class.java)
|
||||
?: error("there is no ${FolderTest::class.java} annotation, can't get file name pattern")
|
||||
val fileNamePattern = folderTest.fileNameFilter.toRegex()
|
||||
|
||||
return Files.list(testFolderPath).filter { it.fileName.toString() matches fileNamePattern }.map { filePath ->
|
||||
val path = filePath.relativeTo(testFolderPath.parent)
|
||||
context.setTestCaseFilePath(path)
|
||||
|
||||
object : TestTemplateInvocationContext {
|
||||
override fun getDisplayName(invocationIndex: Int): String? {
|
||||
return "[$invocationIndex] ${filePath.fileName}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
// 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.python.junit5Tests.framework
|
||||
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiDirectory
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.testFramework.EditorTestUtil
|
||||
import com.intellij.testFramework.IndexingTestUtil
|
||||
import com.intellij.testFramework.junit5.TestApplication
|
||||
import com.intellij.testFramework.junit5.fixture.*
|
||||
import com.intellij.testFramework.junit5.fixture.LookupFixtureExtension.Companion.getLookupFixtureManager
|
||||
import com.intellij.testFramework.junit5.fixture.LookupFixtureExtension.Companion.registerImplicitFixtures
|
||||
import com.jetbrains.python.PyNames
|
||||
import com.intellij.python.junit5Tests.framework.metaInfo.TestMetaInfoExtension
|
||||
import com.intellij.python.junit5Tests.framework.metaInfo.TestMetaInfoExtension.Companion.getTestClassInfo
|
||||
import com.intellij.python.junit5Tests.framework.metaInfo.TestMetaInfoExtension.Companion.getTestMethodInfo
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.junit.jupiter.api.extension.*
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* PyDefaultTestApplication is a test annotation used to initialize a shared application context
|
||||
* and enrich it with various predefined test extensions (project, module, source root, PsiFile, editor, etc).
|
||||
*
|
||||
* It initializes a shared [com.intellij.openapi.application.Application] instance before any tests are run
|
||||
* and disposes it after all tests finish, through the [TestApplication] annotation.
|
||||
*/
|
||||
@TestApplication
|
||||
@ExtendWith(LookupFixtureExtension::class)
|
||||
@ExtendWith(TestMetaInfoExtension::class)
|
||||
@ExtendWith(PyWithDefaultFixturesExtension::class)
|
||||
annotation class PyDefaultTestApplication
|
||||
|
||||
private const val DEFAULT_PROJECT: String = "DEFAULT_PROJECT"
|
||||
private const val DEFAULT_PY_MODULE: String = "DEFAULT_PY_MODULE"
|
||||
private const val DEFAULT_SOURCE_ROOT: String = "DEFAULT_SOURCE_ROOT"
|
||||
private const val DEFAULT_EDITOR: String = "DEFAULT_EDITOR"
|
||||
|
||||
private class PyWithDefaultFixturesExtension : BeforeAllCallback, BeforeEachCallback, Extension {
|
||||
|
||||
/**
|
||||
* Sets up the necessary fixtures and configurations before all tests in the given context. This involves:
|
||||
* - Initializing necessary test project and module fixtures (reuses explicit fixtures if present).
|
||||
* - Setting up project and source root fixtures (reuses explicit fixtures if present).
|
||||
* - Registering all implicitly created fixtures.
|
||||
* - Waiting until all indexes are fully ready for use.
|
||||
*/
|
||||
override fun beforeAll(context: ExtensionContext) {
|
||||
val manager = context.getLookupFixtureManager()
|
||||
|
||||
val implicitFixtures = mutableListOf<LookupFixture>()
|
||||
|
||||
val project = manager.getOrDefault {
|
||||
projectFixture(openAfterCreation = true).also {
|
||||
implicitFixtures += LookupFixture(DEFAULT_PROJECT, it, true)
|
||||
}
|
||||
}
|
||||
|
||||
val module = manager.getOrDefault {
|
||||
project.moduleFixture(name = context.uniqueId, moduleType = PyNames.PYTHON_MODULE_ID).also {
|
||||
implicitFixtures += LookupFixture(DEFAULT_PY_MODULE, it, true)
|
||||
}
|
||||
}
|
||||
|
||||
manager.getOrDefault {
|
||||
module.sourceRootFixture(
|
||||
pathFixture = project.pathInProjectFixture(Path.of("")),
|
||||
blueprintResourcePath = context.getTestClassInfo().testDataPath
|
||||
).also {
|
||||
implicitFixtures += LookupFixture(DEFAULT_SOURCE_ROOT, it, true)
|
||||
}
|
||||
}
|
||||
|
||||
runBlocking {
|
||||
context.registerImplicitFixtures(implicitFixtures, static = true)
|
||||
}
|
||||
|
||||
IndexingTestUtil.waitUntilIndexesAreReady(project.get())
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets up the necessary fixtures and configurations for a test case before it is executed.
|
||||
* This method initializes and registers implicit test fixtures (e.g., PSI file fixture, editor fixture),
|
||||
* configures the editor caret and selection state, ensures documents are saved and committed,
|
||||
* and waits for indexes to be ready.
|
||||
*/
|
||||
override fun beforeEach(context: ExtensionContext) {
|
||||
val testMethodInfo = context.getTestMethodInfo()
|
||||
val testCaseFilePath = testMethodInfo.testCaseFilePath ?: return
|
||||
|
||||
val implicitFixtures = mutableListOf<LookupFixture>()
|
||||
|
||||
val classLevelManager = context.parent.get().getLookupFixtureManager()
|
||||
val sourceRoot: TestFixture<PsiDirectory> = classLevelManager.getRequired()
|
||||
|
||||
val psiFileFixture = sourceRoot.psiFileFixture(testCaseFilePath).also {
|
||||
implicitFixtures += LookupFixture(testCaseFilePath.fileName.toString(), it, true)
|
||||
}
|
||||
|
||||
val editorFixture = psiFileFixture.editorFixture().also {
|
||||
implicitFixtures += LookupFixture(DEFAULT_EDITOR, it, true)
|
||||
}
|
||||
|
||||
runBlocking {
|
||||
context.registerImplicitFixtures(implicitFixtures, static = false)
|
||||
}
|
||||
|
||||
val project = classLevelManager.getRequired<Project>().get()
|
||||
runBlocking {
|
||||
withContext(Dispatchers.EDT) {
|
||||
val editor = editorFixture.get()
|
||||
val document = editor.document
|
||||
|
||||
val caretState = EditorTestUtil.extractCaretAndSelectionMarkers(document)
|
||||
EditorTestUtil.setCaretsAndSelection(editor, caretState)
|
||||
|
||||
FileDocumentManager.getInstance().saveDocument(document)
|
||||
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
||||
}
|
||||
}
|
||||
|
||||
IndexingTestUtil.waitUntilIndexesAreReady(project)
|
||||
}
|
||||
}
|
||||
|
||||
+17
-1
@@ -1,15 +1,20 @@
|
||||
package com.intellij.python.junit5Tests.framework
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.readAction
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.vfs.resolveFromRootOrRelative
|
||||
import com.intellij.platform.util.coroutines.childScope
|
||||
import com.intellij.psi.PsiDirectory
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.testFramework.junit5.fixture.TestFixture
|
||||
import com.intellij.testFramework.junit5.fixture.testFixture
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.cancel
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import java.util.UUID
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
@@ -26,3 +31,14 @@ fun applicationScope(name: String = UUID.randomUUID().toString()): TestFixture<C
|
||||
}
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun TestFixture<PsiDirectory>.psiFileFixture(fileRelativePath: Path): TestFixture<PsiFile> = testFixture { _ ->
|
||||
val sourceRootDirectory = this@psiFileFixture.init()
|
||||
val virtualFile = sourceRootDirectory.virtualFile.resolveFromRootOrRelative(fileRelativePath.toString())
|
||||
?: error("Can't resolve VirtualFile for $fileRelativePath")
|
||||
val psiFile = readAction {
|
||||
sourceRootDirectory.manager.findFile(virtualFile)
|
||||
?: error("Can't find PsiFile for $virtualFile")
|
||||
}
|
||||
initialized(psiFile) {}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// 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.python.junit5Tests.framework.helper
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfo
|
||||
import com.intellij.lang.annotation.HighlightSeverity
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl
|
||||
import com.intellij.util.ArrayUtilRt
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
|
||||
/**
|
||||
* @see CodeInsightTestFixtureImpl.doHighlighting
|
||||
* @see CodeInsightTestFixtureImpl.instantiateAndRun
|
||||
*/
|
||||
@TestOnly
|
||||
fun Editor.doHighlighting(
|
||||
minimalSeverity: HighlightSeverity? = null,
|
||||
canChangeDocument: Boolean = false,
|
||||
readEditorMarkupModel: Boolean = false,
|
||||
): List<HighlightInfo> = runBlocking {
|
||||
val highlightInfos = withContext(Dispatchers.EDT) {
|
||||
requireNotNull(project) { "PsiDocumentManager requires project to be not null" }
|
||||
val psiFile = PsiDocumentManager.getInstance(project!!).getPsiFile(document)
|
||||
CodeInsightTestFixtureImpl.instantiateAndRun(
|
||||
psiFile!!, this@doHighlighting, ArrayUtilRt.EMPTY_INT_ARRAY, canChangeDocument, readEditorMarkupModel
|
||||
)
|
||||
}
|
||||
|
||||
if (minimalSeverity == null) highlightInfos
|
||||
else {
|
||||
highlightInfos.filter { it.severity >= minimalSeverity }
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
// 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.python.junit5Tests.framework.metaInfo
|
||||
|
||||
import com.intellij.openapi.application.PathManager
|
||||
import com.intellij.openapi.application.PluginPathManager
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.junit.jupiter.api.extension.ExtensionContext
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.listDirectoryEntries
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
@TestOnly
|
||||
enum class Repository(val contentRootResolver: (String) -> String) {
|
||||
PY_COMMUNITY({ "${PathManager.getHomePath()}/community/python/${it}" }),
|
||||
PY_PROFESSIONAL({ "${PathManager.getHomePath()}/python/${it}" }),
|
||||
PLUGINS({ PluginPathManager.getPluginHomePath(it) })
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotation for defining $CONTENT_ROOT variable in test classes runtime.
|
||||
*
|
||||
* @property repository Specifies the repository where the test content resides. .
|
||||
* @property contentRootPath Specifies the path in the [repository] which will be used as $CONTENT_ROOT.
|
||||
*/
|
||||
@TestOnly
|
||||
annotation class TestClassInfo(
|
||||
val repository: Repository = Repository.PY_COMMUNITY,
|
||||
val contentRootPath: String = "testSrc",
|
||||
)
|
||||
|
||||
internal fun TestClassInfo.resolvePath(pathWithPlaceholders: String): Path {
|
||||
val contentRootPlaceholder = $$"$CONTENT_ROOT"
|
||||
|
||||
if (!pathWithPlaceholders.contains(contentRootPlaceholder)) {
|
||||
return Path.of(pathWithPlaceholders)
|
||||
}
|
||||
|
||||
val contentRoot = repository.contentRootResolver(contentRootPath)
|
||||
val testDataPath = pathWithPlaceholders.replace(contentRootPlaceholder, contentRoot)
|
||||
|
||||
return Path.of(testDataPath)
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
data class TestClassInfoData(val testDataPath: Path?) {
|
||||
fun getTestResourcePath(fileName: String): Path? {
|
||||
val resources = testDataPath?.listDirectoryEntries("${fileName}{,.*}") ?: return null
|
||||
return when (resources.size) {
|
||||
0 -> null
|
||||
1 -> resources.first()
|
||||
else -> error("Multiple resources found for $fileName: ${resources.joinToString(", ")}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
data class TestMethodInfoData(val testCaseFilePath: Path?)
|
||||
|
||||
|
||||
internal fun <T : Annotation> getAnnotation(context: ExtensionContext?, clazz: Class<T>): T? {
|
||||
return context?.testClass?.map { element -> element.getAnnotation(clazz) }?.getOrNull()
|
||||
}
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
// 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.python.junit5Tests.framework.metaInfo
|
||||
|
||||
import com.intellij.testFramework.PlatformTestUtil
|
||||
import com.intellij.testFramework.TestDataPath
|
||||
import org.junit.jupiter.api.extension.BeforeAllCallback
|
||||
import org.junit.jupiter.api.extension.BeforeEachCallback
|
||||
import org.junit.jupiter.api.extension.Extension
|
||||
import org.junit.jupiter.api.extension.ExtensionContext
|
||||
import org.junit.jupiter.api.extension.ExtensionContext.Namespace
|
||||
import org.junit.jupiter.api.extension.ParameterContext
|
||||
import org.junit.jupiter.api.extension.ParameterResolver
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.relativeTo
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
internal fun ExtensionContext.resolveTestName(): String {
|
||||
return testMethod.map { PlatformTestUtil.getTestName(it.name, true) }.getOrNull()
|
||||
?: error("Can't resolve test name for ${testMethod.map { it.name }}")
|
||||
}
|
||||
|
||||
/**
|
||||
* A JUnit 5 extension that provides test metadata management capabilities at both class and method levels.
|
||||
*
|
||||
* Features of the extension include:
|
||||
* - Resolves and processes class-level and method-level test data paths using custom annotations.
|
||||
* - Injects [TestClassInfoData] and [TestMethodInfoData] as parameters into test methods for easy use.
|
||||
* - Supports deriving test resource paths based on naming conventions and annotations.
|
||||
*/
|
||||
internal class TestMetaInfoExtension : BeforeAllCallback, BeforeEachCallback, Extension, ParameterResolver {
|
||||
companion object {
|
||||
fun ExtensionContext.getTestClassInfo(): TestClassInfoData {
|
||||
val store = getStore(Namespace.GLOBAL)
|
||||
return store.get(TestClassInfoData::class.java) as? TestClassInfoData
|
||||
?: error("${TestClassInfoData::class.java} is not found / not valid in the context")
|
||||
}
|
||||
|
||||
private fun ExtensionContext.setTestClassInfo(testClassInfo: TestClassInfoData) {
|
||||
val store = getStore(Namespace.GLOBAL)
|
||||
return store.put(TestClassInfoData::class.java, testClassInfo)
|
||||
}
|
||||
|
||||
fun ExtensionContext.getTestMethodInfo(): TestMethodInfoData {
|
||||
val store = getStore(Namespace.GLOBAL)
|
||||
return store.get(TestMethodInfoData::class.java) as? TestMethodInfoData
|
||||
?: error("${TestMethodInfoData::class.java} is not found / not valid in the context")
|
||||
}
|
||||
|
||||
private fun ExtensionContext.setTestMethodInfo(testMethodInfo: TestMethodInfoData) {
|
||||
val store = getStore(Namespace.GLOBAL)
|
||||
return store.put(TestMethodInfoData::class.java, testMethodInfo)
|
||||
}
|
||||
|
||||
fun ExtensionContext.setTestCaseFilePath(path: Path) {
|
||||
val store = getStore(Namespace.GLOBAL)
|
||||
return store.put(Path::class.java, path)
|
||||
}
|
||||
|
||||
private fun ExtensionContext.getTestCaseFilePath(): Path? {
|
||||
val store = getStore(Namespace.GLOBAL)
|
||||
return store.get(Path::class.java) as? Path
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class level initialization.
|
||||
* Calculates a real test data path based on class annotations (resolves $CONTENT_ROOT placeholder).
|
||||
*/
|
||||
override fun beforeAll(context: ExtensionContext) {
|
||||
val testClassInfo = getAnnotation(context, TestClassInfo::class.java)
|
||||
?: error("Add ${TestClassInfo::class} class level")
|
||||
|
||||
val testDataPathWithPlaceholders = getAnnotation(context, TestDataPath::class.java)?.value
|
||||
val testDataPath = testDataPathWithPlaceholders?.let { testClassInfo.resolvePath(it) }
|
||||
|
||||
val data = TestClassInfoData(
|
||||
testDataPath = testDataPath,
|
||||
)
|
||||
context.setTestClassInfo(data)
|
||||
}
|
||||
|
||||
override fun beforeEach(context: ExtensionContext) {
|
||||
val explicitPath = context.getTestCaseFilePath()
|
||||
|
||||
val testCaseFilePath = if (explicitPath != null) explicitPath
|
||||
else {
|
||||
val testClassInfo = context.getTestClassInfo()
|
||||
testClassInfo.testDataPath?.let { testDataPath ->
|
||||
val testName = context.resolveTestName()
|
||||
testClassInfo.getTestResourcePath(testName)?.relativeTo(testDataPath)
|
||||
}
|
||||
}
|
||||
|
||||
val data = TestMethodInfoData(
|
||||
testCaseFilePath = testCaseFilePath
|
||||
)
|
||||
context.setTestMethodInfo(data)
|
||||
}
|
||||
|
||||
override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean {
|
||||
return when (parameterContext.parameter.type) {
|
||||
TestClassInfoData::class.java,
|
||||
TestMethodInfoData::class.java,
|
||||
-> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Any {
|
||||
when (parameterContext.parameter.type) {
|
||||
TestClassInfoData::class.java -> extensionContext.getTestClassInfo()
|
||||
TestMethodInfoData::class.java -> extensionContext.getTestMethodInfo()
|
||||
else -> null
|
||||
}?.also { return it }
|
||||
|
||||
error("Not supported parameter received ${parameterContext.parameter.type}")
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
// 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.python.junit5Tests.unit.showCase
|
||||
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.impl.DocumentMarkupModel
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.python.junit5Tests.framework.FolderTest
|
||||
import com.intellij.python.junit5Tests.framework.PyDefaultTestApplication
|
||||
import com.intellij.python.junit5Tests.framework.helper.doHighlighting
|
||||
import com.intellij.python.junit5Tests.framework.metaInfo.Repository
|
||||
import com.intellij.python.junit5Tests.framework.metaInfo.TestClassInfo
|
||||
import com.intellij.testFramework.TestDataPath
|
||||
import com.intellij.testFramework.common.timeoutRunBlocking
|
||||
import com.intellij.testFramework.junit5.fixture.projectFixture
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.CsvSource
|
||||
|
||||
@PyDefaultTestApplication
|
||||
@TestClassInfo(Repository.PY_COMMUNITY)
|
||||
@TestDataPath($$"$CONTENT_ROOT/../junit5Tests-framework/testResources/example/junit5")
|
||||
class PythonJUnit5ExampleTest(
|
||||
val project: Project, /* class-level, the value is projectFixture.get(), might be declared implicitly */
|
||||
val module: Module, /* class-level implicitly declared in PyWithDefaultFixturesExtension */
|
||||
) {
|
||||
companion object {
|
||||
// An explicit override of the default fixture, which can be omitted if the default is enough.
|
||||
private val projectFixture = projectFixture(openAfterCreation = true)
|
||||
}
|
||||
|
||||
/**
|
||||
* test folder iteration is here [com.intellij.python.junit5Tests.framework.AllFilesInFolderTestCaseProvider.provideTestTemplateInvocationContexts]
|
||||
*/
|
||||
@FolderTest // runs testMyFolderTest on each file in the '@TestDataPath/myFolderTest' folder, filtering by regex is supported
|
||||
fun testHighlighting(
|
||||
file: PsiFile, /* implicitly declared in PyWithDefaultFixturesExtension on the test method level */
|
||||
editor: Editor, /* implicitly declared in PyWithDefaultFixturesExtension on the test method level */
|
||||
) {
|
||||
editor.doHighlighting()
|
||||
|
||||
val doc = PsiDocumentManager.getInstance(project).getDocument(file)!!
|
||||
val markupModel = DocumentMarkupModel.forDocument(doc, project, false)
|
||||
val highlighters = markupModel.allHighlighters
|
||||
assert(highlighters.isNotEmpty())
|
||||
}
|
||||
|
||||
/**
|
||||
* resource recognition by test name is here [com.intellij.python.junit5Tests.framework.metaInfo.TestClassInfoData.getTestResourcePath]
|
||||
*/
|
||||
@Test
|
||||
fun testSingle(
|
||||
psiFile: PsiFile, /* testMyTestName -> myTestName.* (should be a single file with this name) in the @TestDataPath folder */
|
||||
) = timeoutRunBlocking {
|
||||
withContext(Dispatchers.EDT) {
|
||||
Assertions.assertEquals("print(\"Hello, world!\")\n", psiFile.text)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an example of a parametrized test with multiple cases provided.
|
||||
* The one could write expectations among the parameters as well.
|
||||
*
|
||||
* @param fileName the name of the file being tested
|
||||
* @param length the expected length of the file name
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@CsvSource(value = [
|
||||
"first.py,8",
|
||||
"second.py,9",
|
||||
])
|
||||
fun testMultipleParameters(fileName: String, length: Int) {
|
||||
Assertions.assertEquals(length, fileName.length)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import hello_world
|
||||
|
||||
if __name__ == "__main__":
|
||||
main = Main()<caret>
|
||||
print("Hello, World!")
|
||||
@@ -0,0 +1,2 @@
|
||||
class Main:
|
||||
pass<caret>
|
||||
@@ -0,0 +1 @@
|
||||
print("Hello, world!")<caret>
|
||||
Reference in New Issue
Block a user