Scripting loading tests and extension points
This commit is contained in:
committed by
Natalia Selezneva
parent
ca71b2fe90
commit
0fb58ed94c
+16
-5
@@ -14,13 +14,10 @@ import com.intellij.openapi.util.Ref
|
|||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import com.intellij.ui.EditorNotificationPanel
|
import com.intellij.ui.EditorNotificationPanel
|
||||||
import com.intellij.ui.HyperlinkLabel
|
import com.intellij.ui.HyperlinkLabel
|
||||||
|
import org.jetbrains.annotations.TestOnly
|
||||||
import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings
|
import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings
|
||||||
import org.jetbrains.kotlin.psi.UserDataProperty
|
import org.jetbrains.kotlin.psi.UserDataProperty
|
||||||
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
import org.jetbrains.kotlin.scripting.resolve.ScriptCompilationConfigurationWrapper
|
||||||
import java.awt.event.ComponentAdapter
|
|
||||||
import java.awt.event.ComponentEvent
|
|
||||||
import java.awt.event.ComponentListener
|
|
||||||
import javax.swing.JPanel
|
|
||||||
|
|
||||||
fun VirtualFile.removeScriptDependenciesNotificationPanel(project: Project) {
|
fun VirtualFile.removeScriptDependenciesNotificationPanel(project: Project) {
|
||||||
withSelectedEditor(project) { manager ->
|
withSelectedEditor(project) { manager ->
|
||||||
@@ -53,6 +50,20 @@ fun VirtualFile.addScriptDependenciesNotificationPanel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestOnly
|
||||||
|
fun VirtualFile.hasSuggestedScriptConfiguration(project: Project): Boolean {
|
||||||
|
return FileEditorManager.getInstance(project).getSelectedEditor(this)?.notificationPanel != null
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOnly
|
||||||
|
fun VirtualFile.applySuggestedScriptConfiguration(project: Project): Boolean {
|
||||||
|
val notificationPanel = FileEditorManager.getInstance(project).getSelectedEditor(this)?.notificationPanel
|
||||||
|
?: return false
|
||||||
|
notificationPanel.onClick.invoke()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private fun VirtualFile.withSelectedEditor(project: Project, f: FileEditor.(FileEditorManager) -> Unit) {
|
private fun VirtualFile.withSelectedEditor(project: Project, f: FileEditor.(FileEditorManager) -> Unit) {
|
||||||
ApplicationManager.getApplication().invokeLater {
|
ApplicationManager.getApplication().invokeLater {
|
||||||
if (project.isDisposed) return@invokeLater
|
if (project.isDisposed) return@invokeLater
|
||||||
@@ -67,7 +78,7 @@ private fun VirtualFile.withSelectedEditor(project: Project, f: FileEditor.(File
|
|||||||
private var FileEditor.notificationPanel: NewScriptDependenciesNotificationPanel? by UserDataProperty<FileEditor, NewScriptDependenciesNotificationPanel>(Key.create("script.dependencies.panel"))
|
private var FileEditor.notificationPanel: NewScriptDependenciesNotificationPanel? by UserDataProperty<FileEditor, NewScriptDependenciesNotificationPanel>(Key.create("script.dependencies.panel"))
|
||||||
|
|
||||||
private class NewScriptDependenciesNotificationPanel(
|
private class NewScriptDependenciesNotificationPanel(
|
||||||
onClick: () -> Unit,
|
val onClick: () -> Unit,
|
||||||
val compilationConfigurationResult: ScriptCompilationConfigurationWrapper,
|
val compilationConfigurationResult: ScriptCompilationConfigurationWrapper,
|
||||||
project: Project
|
project: Project
|
||||||
) : EditorNotificationPanel() {
|
) : EditorNotificationPanel() {
|
||||||
|
|||||||
+34
-12
@@ -8,6 +8,8 @@ package org.jetbrains.kotlin.idea.core.script.configuration
|
|||||||
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
|
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.components.ServiceManager
|
import com.intellij.openapi.components.ServiceManager
|
||||||
|
import com.intellij.openapi.extensions.ExtensionPointName
|
||||||
|
import com.intellij.openapi.extensions.Extensions
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import com.intellij.psi.PsiManager
|
import com.intellij.psi.PsiManager
|
||||||
@@ -15,6 +17,7 @@ import com.intellij.ui.EditorNotifications
|
|||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jetbrains.kotlin.idea.core.script.*
|
import org.jetbrains.kotlin.idea.core.script.*
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptConfigurationManagerExtensions.LOADER
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationFileAttributeCache
|
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationFileAttributeCache
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationMemoryCache
|
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationMemoryCache
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationSnapshot
|
import org.jetbrains.kotlin.idea.core.script.configuration.cache.ScriptConfigurationSnapshot
|
||||||
@@ -27,6 +30,9 @@ import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigur
|
|||||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoadingContext
|
import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoadingContext
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptOutsiderFileConfigurationLoader
|
import org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptOutsiderFileConfigurationLoader
|
||||||
import org.jetbrains.kotlin.idea.core.script.configuration.utils.BackgroundExecutor
|
import org.jetbrains.kotlin.idea.core.script.configuration.utils.BackgroundExecutor
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.utils.DefaultBackgroundExecutor
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.utils.TestingBackgroundExecutor
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.utils.isUnitTestModeWithoutScriptLoadingNotification
|
||||||
import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings
|
import org.jetbrains.kotlin.idea.core.script.settings.KotlinScriptingSettings
|
||||||
import org.jetbrains.kotlin.idea.core.util.EDT
|
import org.jetbrains.kotlin.idea.core.util.EDT
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
@@ -96,21 +102,23 @@ import kotlin.script.experimental.api.ScriptDiagnostic
|
|||||||
*/
|
*/
|
||||||
internal class DefaultScriptConfigurationManager(project: Project) :
|
internal class DefaultScriptConfigurationManager(project: Project) :
|
||||||
AbstractScriptConfigurationManager(project) {
|
AbstractScriptConfigurationManager(project) {
|
||||||
private val backgroundExecutor = BackgroundExecutor(project, rootsIndexer)
|
|
||||||
|
internal val backgroundExecutor: BackgroundExecutor =
|
||||||
|
if (ApplicationManager.getApplication().isUnitTestMode) TestingBackgroundExecutor(rootsIndexer)
|
||||||
|
else DefaultBackgroundExecutor(project, rootsIndexer)
|
||||||
|
|
||||||
private val fileAttributeCache = ScriptConfigurationFileAttributeCache(project)
|
private val fileAttributeCache = ScriptConfigurationFileAttributeCache(project)
|
||||||
|
|
||||||
private val loaders: List<ScriptConfigurationLoader> = listOf(
|
private val loaders: List<ScriptConfigurationLoader>
|
||||||
ScriptOutsiderFileConfigurationLoader(project),
|
get() = listOf(
|
||||||
fileAttributeCache,
|
ScriptOutsiderFileConfigurationLoader(project),
|
||||||
GradleScriptConfigurationLoader(project),
|
ScriptConfigurationFileAttributeCache(project)
|
||||||
DefaultScriptConfigurationLoader(project)
|
) + project[LOADER] + listOf(
|
||||||
)
|
DefaultScriptConfigurationLoader(project)
|
||||||
|
)
|
||||||
|
|
||||||
private val listeners: List<ScriptChangeListener> = listOf(
|
private val listeners: List<ScriptChangeListener>
|
||||||
GradleScriptListener(),
|
get() = project[DefaultScriptConfigurationManagerExtensions.LISTENER] + listOf(DefaultScriptChangeListener())
|
||||||
DefaultScriptChangeListener()
|
|
||||||
)
|
|
||||||
|
|
||||||
private val notifier = ScriptChangesNotifier(project, updater, listeners)
|
private val notifier = ScriptChangesNotifier(project, updater, listeners)
|
||||||
|
|
||||||
@@ -123,6 +131,9 @@ internal class DefaultScriptConfigurationManager(project: Project) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private operator fun <T> Project.get(epName: ExtensionPointName<T>): List<T> =
|
||||||
|
Extensions.getArea(this).getExtensionPoint(epName).extensionList
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will be called on [cache] miss to initiate loading of [file]'s script configuration.
|
* Will be called on [cache] miss to initiate loading of [file]'s script configuration.
|
||||||
*
|
*
|
||||||
@@ -248,7 +259,7 @@ internal class DefaultScriptConfigurationManager(project: Project) :
|
|||||||
val autoReload = skipNotification
|
val autoReload = skipNotification
|
||||||
|| oldConfiguration == null
|
|| oldConfiguration == null
|
||||||
|| KotlinScriptingSettings.getInstance(project).isAutoReloadEnabled
|
|| KotlinScriptingSettings.getInstance(project).isAutoReloadEnabled
|
||||||
|| ApplicationManager.getApplication().isUnitTestMode
|
|| ApplicationManager.getApplication().isUnitTestModeWithoutScriptLoadingNotification
|
||||||
|
|
||||||
if (autoReload) {
|
if (autoReload) {
|
||||||
if (oldConfiguration != null) {
|
if (oldConfiguration != null) {
|
||||||
@@ -304,3 +315,14 @@ internal class DefaultScriptConfigurationManager(project: Project) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object DefaultScriptConfigurationManagerExtensions {
|
||||||
|
val LOADER: ExtensionPointName<ScriptConfigurationLoader> =
|
||||||
|
ExtensionPointName.create("org.jetbrains.kotlin.scripting.idea.loader")
|
||||||
|
|
||||||
|
val LISTENER: ExtensionPointName<ScriptChangeListener> =
|
||||||
|
ExtensionPointName.create("org.jetbrains.kotlin.scripting.idea.listener")
|
||||||
|
}
|
||||||
|
|
||||||
|
val ScriptConfigurationManager.testingBackgroundExecutor
|
||||||
|
get() = (this as DefaultScriptConfigurationManager).backgroundExecutor as TestingBackgroundExecutor
|
||||||
|
|||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.core.script.configuration.loader
|
||||||
|
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.cache.CachedConfigurationInputs
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
|
||||||
|
open class FileContentsDependentConfigurationLoader(project: Project) : DefaultScriptConfigurationLoader(project) {
|
||||||
|
override fun getInputsStamp(virtualFile: VirtualFile, file: KtFile): CachedConfigurationInputs {
|
||||||
|
return CachedConfigurationInputs.SourceContentsStamp.get(project, virtualFile, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -13,7 +13,7 @@ import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
|
|||||||
*
|
*
|
||||||
* @see [org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptConfigurationManager] for more details.
|
* @see [org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptConfigurationManager] for more details.
|
||||||
*/
|
*/
|
||||||
internal interface ScriptConfigurationLoader {
|
interface ScriptConfigurationLoader {
|
||||||
fun shouldRunInBackground(scriptDefinition: ScriptDefinition): Boolean = false
|
fun shouldRunInBackground(scriptDefinition: ScriptDefinition): Boolean = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+2
-230
@@ -5,236 +5,8 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.core.script.configuration.utils
|
package org.jetbrains.kotlin.idea.core.script.configuration.utils
|
||||||
|
|
||||||
import com.intellij.openapi.progress.ProgressIndicator
|
|
||||||
import com.intellij.openapi.progress.Task
|
|
||||||
import com.intellij.openapi.progress.util.BackgroundTaskUtil
|
|
||||||
import com.intellij.openapi.project.Project
|
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import com.intellij.util.Alarm
|
|
||||||
import com.intellij.util.containers.HashSetQueue
|
|
||||||
import org.jetbrains.kotlin.idea.core.script.debug
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
interface BackgroundExecutor {
|
||||||
* Sequentially loads script configuration in background.
|
fun ensureScheduled(key: VirtualFile, actions: () -> Unit)
|
||||||
* Loading tasks scheduled by calling [ensureScheduled].
|
|
||||||
*
|
|
||||||
* Progress indicator will be shown after [PROGRESS_INDICATOR_DELAY] ms or if
|
|
||||||
* more then [PROGRESS_INDICATOR_MIN_QUEUE] tasks scheduled.
|
|
||||||
*
|
|
||||||
* States:
|
|
||||||
* silentWorker underProgressWorker
|
|
||||||
* - sleep
|
|
||||||
* - silent x
|
|
||||||
* - silent and under progress x x
|
|
||||||
* - under progress x
|
|
||||||
*/
|
|
||||||
internal class BackgroundExecutor(
|
|
||||||
val project: Project,
|
|
||||||
val rootsManager: ScriptClassRootsIndexer
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
const val PROGRESS_INDICATOR_DELAY = 1000
|
|
||||||
const val PROGRESS_INDICATOR_MIN_QUEUE = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
private val work = Any()
|
|
||||||
private val queue: Queue<LoadTask> = HashSetQueue()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Let's fix queue size when progress bar displayed.
|
|
||||||
* Progress for rest items will be counted from zero
|
|
||||||
*/
|
|
||||||
private var currentProgressSize: Int = 0
|
|
||||||
private var currentProgressDone: Int = 0
|
|
||||||
|
|
||||||
private var silentWorker: SilentWorker? = null
|
|
||||||
private var underProgressWorker: UnderProgressWorker? = null
|
|
||||||
private val longRunningAlarm = Alarm(Alarm.ThreadToUse.POOLED_THREAD, project)
|
|
||||||
private var longRunningAlaramRequested = false
|
|
||||||
|
|
||||||
private var inTransaction: Boolean = false
|
|
||||||
private var currentFile: VirtualFile? = null
|
|
||||||
|
|
||||||
class LoadTask(val key: VirtualFile, val actions: () -> Unit) {
|
|
||||||
override fun equals(other: Any?) =
|
|
||||||
this === other || (other is LoadTask && key == other.key)
|
|
||||||
|
|
||||||
override fun hashCode() = key.hashCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun ensureScheduled(key: VirtualFile, actions: () -> Unit) {
|
|
||||||
val task = LoadTask(key, actions)
|
|
||||||
|
|
||||||
if (queue.add(task)) {
|
|
||||||
debug(task.key) { "added to update queue" }
|
|
||||||
|
|
||||||
// If the queue is longer than PROGRESS_INDICATOR_MIN_QUEUE, show progress and cancel button
|
|
||||||
if (queue.size > PROGRESS_INDICATOR_MIN_QUEUE) {
|
|
||||||
requireUnderProgressWorker()
|
|
||||||
} else {
|
|
||||||
requireSilentWorker()
|
|
||||||
|
|
||||||
if (!longRunningAlaramRequested) {
|
|
||||||
longRunningAlaramRequested = true
|
|
||||||
longRunningAlarm.addRequest(
|
|
||||||
{
|
|
||||||
longRunningAlaramRequested = false
|
|
||||||
requireUnderProgressWorker()
|
|
||||||
},
|
|
||||||
PROGRESS_INDICATOR_DELAY
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun requireSilentWorker() {
|
|
||||||
if (silentWorker == null && underProgressWorker == null) {
|
|
||||||
silentWorker = SilentWorker().also { it.start() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun requireUnderProgressWorker() {
|
|
||||||
if (queue.isEmpty() && silentWorker == null) return
|
|
||||||
|
|
||||||
silentWorker?.stopGracefully()
|
|
||||||
if (underProgressWorker == null) {
|
|
||||||
underProgressWorker = UnderProgressWorker().also { it.start() }
|
|
||||||
restartProgressBar()
|
|
||||||
updateProgress()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun restartProgressBar() {
|
|
||||||
currentProgressSize = queue.size
|
|
||||||
currentProgressDone = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun updateProgress() {
|
|
||||||
underProgressWorker?.progressIndicator?.let {
|
|
||||||
it.text2 = currentFile?.path ?: ""
|
|
||||||
if (queue.size == 0) {
|
|
||||||
// last file
|
|
||||||
it.isIndeterminate = true
|
|
||||||
} else {
|
|
||||||
it.isIndeterminate = false
|
|
||||||
if (currentProgressDone > currentProgressSize) {
|
|
||||||
restartProgressBar()
|
|
||||||
}
|
|
||||||
it.fraction = currentProgressDone.toDouble() / currentProgressSize.toDouble()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun ensureInTransaction() {
|
|
||||||
if (inTransaction) return
|
|
||||||
inTransaction = true
|
|
||||||
rootsManager.startTransaction()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun endBatch() {
|
|
||||||
check(inTransaction)
|
|
||||||
rootsManager.commit()
|
|
||||||
inTransaction = false
|
|
||||||
}
|
|
||||||
|
|
||||||
private abstract inner class Worker {
|
|
||||||
private var shouldStop = false
|
|
||||||
|
|
||||||
open fun start() {
|
|
||||||
ensureInTransaction()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun stopGracefully() {
|
|
||||||
shouldStop = true
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun checkCancelled() = false
|
|
||||||
protected abstract fun close()
|
|
||||||
|
|
||||||
protected fun run() {
|
|
||||||
try {
|
|
||||||
while (true) {
|
|
||||||
// prevent parallel work in both silent and under progress
|
|
||||||
synchronized(work) {
|
|
||||||
val next = synchronized(this@BackgroundExecutor) {
|
|
||||||
if (shouldStop) return
|
|
||||||
|
|
||||||
if (checkCancelled()) {
|
|
||||||
queue.clear()
|
|
||||||
endBatch()
|
|
||||||
return
|
|
||||||
} else if (queue.isEmpty()) {
|
|
||||||
endBatch()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
queue.poll()?.also {
|
|
||||||
currentFile = it.key
|
|
||||||
currentProgressDone++
|
|
||||||
updateProgress()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
next?.actions?.invoke()
|
|
||||||
|
|
||||||
synchronized(work) {
|
|
||||||
currentFile = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class UnderProgressWorker : Worker() {
|
|
||||||
var progressIndicator: ProgressIndicator? = null
|
|
||||||
|
|
||||||
override fun start() {
|
|
||||||
super.start()
|
|
||||||
|
|
||||||
object : Task.Backgroundable(project, "Kotlin: Loading script dependencies...", true) {
|
|
||||||
override fun run(indicator: ProgressIndicator) {
|
|
||||||
progressIndicator = indicator
|
|
||||||
updateProgress()
|
|
||||||
run()
|
|
||||||
}
|
|
||||||
}.queue()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun checkCancelled(): Boolean = progressIndicator?.isCanceled == true
|
|
||||||
|
|
||||||
override fun close() {
|
|
||||||
synchronized(this@BackgroundExecutor) {
|
|
||||||
underProgressWorker = null
|
|
||||||
progressIndicator = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class SilentWorker : Worker() {
|
|
||||||
override fun start() {
|
|
||||||
super.start()
|
|
||||||
|
|
||||||
BackgroundTaskUtil.executeOnPooledThread(project, Runnable {
|
|
||||||
run()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() {
|
|
||||||
synchronized(this@BackgroundExecutor) {
|
|
||||||
silentWorker = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+241
@@ -0,0 +1,241 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.core.script.configuration.utils
|
||||||
|
|
||||||
|
import com.intellij.openapi.progress.ProgressIndicator
|
||||||
|
import com.intellij.openapi.progress.Task
|
||||||
|
import com.intellij.openapi.progress.util.BackgroundTaskUtil
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import com.intellij.util.Alarm
|
||||||
|
import com.intellij.util.containers.HashSetQueue
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.debug
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sequentially loads script configuration in background.
|
||||||
|
* Loading tasks scheduled by calling [ensureScheduled].
|
||||||
|
*
|
||||||
|
* Progress indicator will be shown after [PROGRESS_INDICATOR_DELAY] ms or if
|
||||||
|
* more then [PROGRESS_INDICATOR_MIN_QUEUE] tasks scheduled.
|
||||||
|
*
|
||||||
|
* States:
|
||||||
|
* silentWorker underProgressWorker
|
||||||
|
* - sleep
|
||||||
|
* - silent x
|
||||||
|
* - silent and under progress x x
|
||||||
|
* - under progress x
|
||||||
|
*/
|
||||||
|
internal class DefaultBackgroundExecutor(
|
||||||
|
val project: Project,
|
||||||
|
val rootsManager: ScriptClassRootsIndexer
|
||||||
|
) : BackgroundExecutor {
|
||||||
|
companion object {
|
||||||
|
const val PROGRESS_INDICATOR_DELAY = 1000
|
||||||
|
const val PROGRESS_INDICATOR_MIN_QUEUE = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
private val work = Any()
|
||||||
|
private val queue: Queue<LoadTask> = HashSetQueue()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Let's fix queue size when progress bar displayed.
|
||||||
|
* Progress for rest items will be counted from zero
|
||||||
|
*/
|
||||||
|
private var currentProgressSize: Int = 0
|
||||||
|
private var currentProgressDone: Int = 0
|
||||||
|
|
||||||
|
private var silentWorker: SilentWorker? = null
|
||||||
|
private var underProgressWorker: UnderProgressWorker? = null
|
||||||
|
private val longRunningAlarm = Alarm(Alarm.ThreadToUse.POOLED_THREAD, project)
|
||||||
|
private var longRunningAlaramRequested = false
|
||||||
|
|
||||||
|
private var inTransaction: Boolean = false
|
||||||
|
private var currentFile: VirtualFile? = null
|
||||||
|
|
||||||
|
class LoadTask(val key: VirtualFile, val actions: () -> Unit) {
|
||||||
|
override fun equals(other: Any?) =
|
||||||
|
this === other || (other is LoadTask && key == other.key)
|
||||||
|
|
||||||
|
override fun hashCode() = key.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun ensureScheduled(key: VirtualFile, actions: () -> Unit) {
|
||||||
|
val task = LoadTask(key, actions)
|
||||||
|
|
||||||
|
if (queue.add(task)) {
|
||||||
|
debug(task.key) { "added to update queue" }
|
||||||
|
|
||||||
|
// If the queue is longer than PROGRESS_INDICATOR_MIN_QUEUE, show progress and cancel button
|
||||||
|
if (queue.size > PROGRESS_INDICATOR_MIN_QUEUE) {
|
||||||
|
requireUnderProgressWorker()
|
||||||
|
} else {
|
||||||
|
requireSilentWorker()
|
||||||
|
|
||||||
|
if (!longRunningAlaramRequested) {
|
||||||
|
longRunningAlaramRequested = true
|
||||||
|
longRunningAlarm.addRequest(
|
||||||
|
{
|
||||||
|
longRunningAlaramRequested = false
|
||||||
|
requireUnderProgressWorker()
|
||||||
|
},
|
||||||
|
PROGRESS_INDICATOR_DELAY
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun requireSilentWorker() {
|
||||||
|
if (silentWorker == null && underProgressWorker == null) {
|
||||||
|
silentWorker = SilentWorker().also { it.start() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun requireUnderProgressWorker() {
|
||||||
|
if (queue.isEmpty() && silentWorker == null) return
|
||||||
|
|
||||||
|
silentWorker?.stopGracefully()
|
||||||
|
if (underProgressWorker == null) {
|
||||||
|
underProgressWorker = UnderProgressWorker().also { it.start() }
|
||||||
|
restartProgressBar()
|
||||||
|
updateProgress()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun restartProgressBar() {
|
||||||
|
currentProgressSize = queue.size
|
||||||
|
currentProgressDone = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun updateProgress() {
|
||||||
|
underProgressWorker?.progressIndicator?.let {
|
||||||
|
it.text2 = currentFile?.path ?: ""
|
||||||
|
if (queue.size == 0) {
|
||||||
|
// last file
|
||||||
|
it.isIndeterminate = true
|
||||||
|
} else {
|
||||||
|
it.isIndeterminate = false
|
||||||
|
if (currentProgressDone > currentProgressSize) {
|
||||||
|
restartProgressBar()
|
||||||
|
}
|
||||||
|
it.fraction = currentProgressDone.toDouble() / currentProgressSize.toDouble()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun ensureInTransaction() {
|
||||||
|
if (inTransaction) return
|
||||||
|
inTransaction = true
|
||||||
|
rootsManager.startTransaction()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun endBatch() {
|
||||||
|
check(inTransaction)
|
||||||
|
rootsManager.commit()
|
||||||
|
inTransaction = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract inner class Worker {
|
||||||
|
private var shouldStop = false
|
||||||
|
|
||||||
|
open fun start() {
|
||||||
|
ensureInTransaction()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopGracefully() {
|
||||||
|
shouldStop = true
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun checkCancelled() = false
|
||||||
|
protected abstract fun close()
|
||||||
|
|
||||||
|
protected fun run() {
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
// prevent parallel work in both silent and under progress
|
||||||
|
synchronized(work) {
|
||||||
|
val next = synchronized(this@DefaultBackgroundExecutor) {
|
||||||
|
if (shouldStop) return
|
||||||
|
|
||||||
|
if (checkCancelled()) {
|
||||||
|
queue.clear()
|
||||||
|
endBatch()
|
||||||
|
return
|
||||||
|
} else if (queue.isEmpty()) {
|
||||||
|
endBatch()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.poll()?.also {
|
||||||
|
currentFile = it.key
|
||||||
|
currentProgressDone++
|
||||||
|
updateProgress()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next?.actions?.invoke()
|
||||||
|
|
||||||
|
synchronized(work) {
|
||||||
|
currentFile = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class UnderProgressWorker : Worker() {
|
||||||
|
var progressIndicator: ProgressIndicator? = null
|
||||||
|
|
||||||
|
override fun start() {
|
||||||
|
super.start()
|
||||||
|
|
||||||
|
object : Task.Backgroundable(project, "Kotlin: Loading script dependencies...", true) {
|
||||||
|
override fun run(indicator: ProgressIndicator) {
|
||||||
|
progressIndicator = indicator
|
||||||
|
updateProgress()
|
||||||
|
run()
|
||||||
|
}
|
||||||
|
}.queue()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun checkCancelled(): Boolean = progressIndicator?.isCanceled == true
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
synchronized(this@DefaultBackgroundExecutor) {
|
||||||
|
underProgressWorker = null
|
||||||
|
progressIndicator = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class SilentWorker : Worker() {
|
||||||
|
override fun start() {
|
||||||
|
super.start()
|
||||||
|
|
||||||
|
BackgroundTaskUtil.executeOnPooledThread(project, Runnable {
|
||||||
|
run()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
synchronized(this@DefaultBackgroundExecutor) {
|
||||||
|
silentWorker = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.core.script.configuration.utils
|
||||||
|
|
||||||
|
import com.intellij.openapi.application.impl.LaterInvocator
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import com.intellij.util.containers.HashSetQueue
|
||||||
|
|
||||||
|
class TestingBackgroundExecutor internal constructor(
|
||||||
|
private val rootsManager: ScriptClassRootsIndexer
|
||||||
|
) : BackgroundExecutor {
|
||||||
|
val backgroundQueue = HashSetQueue<BackgroundTask>()
|
||||||
|
|
||||||
|
class BackgroundTask(val file: VirtualFile, val actions: () -> Unit) {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as BackgroundTask
|
||||||
|
|
||||||
|
if (file != other.file) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return file.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun ensureScheduled(key: VirtualFile, actions: () -> Unit) {
|
||||||
|
backgroundQueue.add(BackgroundTask(key, actions))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun doAllBackgroundTaskWith(actions: () -> Unit): Boolean {
|
||||||
|
val copy = backgroundQueue.toList()
|
||||||
|
backgroundQueue.clear()
|
||||||
|
|
||||||
|
actions()
|
||||||
|
|
||||||
|
rootsManager.transaction {
|
||||||
|
copy.forEach {
|
||||||
|
it.actions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaterInvocator.ensureFlushRequested()
|
||||||
|
LaterInvocator.dispatchPendingFlushes()
|
||||||
|
|
||||||
|
return copy.isNotEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.core.script.configuration.utils
|
||||||
|
|
||||||
|
import com.intellij.openapi.application.Application
|
||||||
|
|
||||||
|
var testScriptConfigurationNotification: Boolean = false
|
||||||
|
|
||||||
|
val Application.isUnitTestModeWithoutScriptLoadingNotification: Boolean
|
||||||
|
get() = isUnitTestMode && !testScriptConfigurationNotification
|
||||||
@@ -59,6 +59,14 @@
|
|||||||
|
|
||||||
<extensionPoint qualifiedName="org.jetbrains.kotlin.scriptDiagnosticFixProvider"
|
<extensionPoint qualifiedName="org.jetbrains.kotlin.scriptDiagnosticFixProvider"
|
||||||
interface="org.jetbrains.kotlin.idea.script.ScriptDiagnosticFixProvider"/>
|
interface="org.jetbrains.kotlin.idea.script.ScriptDiagnosticFixProvider"/>
|
||||||
|
|
||||||
|
<extensionPoint qualifiedName="org.jetbrains.kotlin.scripting.idea.loader"
|
||||||
|
interface="org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoader"
|
||||||
|
area="IDEA_PROJECT"/>
|
||||||
|
|
||||||
|
<extensionPoint qualifiedName="org.jetbrains.kotlin.scripting.idea.listener"
|
||||||
|
interface="org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener"
|
||||||
|
area="IDEA_PROJECT"/>
|
||||||
</extensionPoints>
|
</extensionPoints>
|
||||||
|
|
||||||
<extensions defaultExtensionNs="org.jetbrains.kotlin">
|
<extensions defaultExtensionNs="org.jetbrains.kotlin">
|
||||||
|
|||||||
@@ -67,6 +67,14 @@
|
|||||||
|
|
||||||
<extensionPoint qualifiedName="org.jetbrains.kotlin.scriptDiagnosticFixProvider"
|
<extensionPoint qualifiedName="org.jetbrains.kotlin.scriptDiagnosticFixProvider"
|
||||||
interface="org.jetbrains.kotlin.idea.script.ScriptDiagnosticFixProvider"/>
|
interface="org.jetbrains.kotlin.idea.script.ScriptDiagnosticFixProvider"/>
|
||||||
|
|
||||||
|
<extensionPoint qualifiedName="org.jetbrains.kotlin.scripting.idea.loader"
|
||||||
|
interface="org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoader"
|
||||||
|
area="IDEA_PROJECT"/>
|
||||||
|
|
||||||
|
<extensionPoint qualifiedName="org.jetbrains.kotlin.scripting.idea.listener"
|
||||||
|
interface="org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener"
|
||||||
|
area="IDEA_PROJECT"/>
|
||||||
</extensionPoints>
|
</extensionPoints>
|
||||||
|
|
||||||
<extensions defaultExtensionNs="org.jetbrains.kotlin">
|
<extensions defaultExtensionNs="org.jetbrains.kotlin">
|
||||||
|
|||||||
@@ -59,6 +59,14 @@
|
|||||||
|
|
||||||
<extensionPoint qualifiedName="org.jetbrains.kotlin.scriptDiagnosticFixProvider"
|
<extensionPoint qualifiedName="org.jetbrains.kotlin.scriptDiagnosticFixProvider"
|
||||||
interface="org.jetbrains.kotlin.idea.script.ScriptDiagnosticFixProvider"/>
|
interface="org.jetbrains.kotlin.idea.script.ScriptDiagnosticFixProvider"/>
|
||||||
|
|
||||||
|
<extensionPoint qualifiedName="org.jetbrains.kotlin.scripting.idea.loader"
|
||||||
|
interface="org.jetbrains.kotlin.idea.core.script.configuration.loader.ScriptConfigurationLoader"
|
||||||
|
area="IDEA_PROJECT"/>
|
||||||
|
|
||||||
|
<extensionPoint qualifiedName="org.jetbrains.kotlin.scripting.idea.listener"
|
||||||
|
interface="org.jetbrains.kotlin.idea.core.script.configuration.listener.ScriptChangeListener"
|
||||||
|
area="IDEA_PROJECT"/>
|
||||||
</extensionPoints>
|
</extensionPoints>
|
||||||
|
|
||||||
<extensions defaultExtensionNs="org.jetbrains.kotlin">
|
<extensions defaultExtensionNs="org.jetbrains.kotlin">
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
initial
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package custom.scriptDefinition
|
||||||
|
|
||||||
|
import kotlin.script.dependencies.*
|
||||||
|
import kotlin.script.experimental.dependencies.*
|
||||||
|
import kotlin.script.templates.*
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.script.experimental.location.*
|
||||||
|
|
||||||
|
class TestDependenciesResolver : AsyncDependenciesResolver {
|
||||||
|
suspend override fun resolveAsync(scriptContents: ScriptContents, environment: Environment): DependenciesResolver.ResolveResult {
|
||||||
|
val text = scriptContents.text as String
|
||||||
|
|
||||||
|
val result = when {
|
||||||
|
text.startsWith("#BAD:") -> DependenciesResolver.ResolveResult.Failure(
|
||||||
|
reports = listOf(ScriptReport(text))
|
||||||
|
)
|
||||||
|
else -> DependenciesResolver.ResolveResult.Success(
|
||||||
|
dependencies = ScriptDependencies(
|
||||||
|
classpath = listOf(environment["template-classes"] as File),
|
||||||
|
imports = listOf("x_" + text.replace(Regex("#IGNORE_IN_CONFIGURATION"), ""))
|
||||||
|
),
|
||||||
|
reports = listOf(ScriptReport(text))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
javaClass.classLoader
|
||||||
|
.loadClass("org.jetbrains.kotlin.idea.script.ScriptConfigurationLoadingTest")
|
||||||
|
.methods.single { it.name == "loadingScriptConfigurationCallback" }
|
||||||
|
.invoke(null)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ScriptExpectedLocations([ScriptExpectedLocation.Everywhere])
|
||||||
|
@ScriptTemplateDefinition(TestDependenciesResolver::class, scriptFilePattern = "script.kts")
|
||||||
|
class Template : Base()
|
||||||
|
|
||||||
|
open class Base {
|
||||||
|
val i = 3
|
||||||
|
val str = ""
|
||||||
|
}
|
||||||
+145
@@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.script
|
||||||
|
|
||||||
|
import com.intellij.openapi.components.ServiceManager
|
||||||
|
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.IdeScriptReportSink
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.ScriptConfigurationManager
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.applySuggestedScriptConfiguration
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.DefaultScriptConfigurationManagerExtensions
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.loader.FileContentsDependentConfigurationLoader
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.testingBackgroundExecutor
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.configuration.utils.testScriptConfigurationNotification
|
||||||
|
import org.jetbrains.kotlin.idea.core.script.hasSuggestedScriptConfiguration
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
|
||||||
|
abstract class AbstractScriptConfigurationLoadingTest : AbstractScriptConfigurationTest() {
|
||||||
|
val ktFile: KtFile get() = myFile as KtFile
|
||||||
|
val virtualFile get() = myFile.virtualFile
|
||||||
|
|
||||||
|
lateinit var scriptConfigurationManager: ScriptConfigurationManager
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private var occurredLoadings = 0
|
||||||
|
private var currentLoadingScriptConfigurationCallback: (() -> Unit)? = null
|
||||||
|
@JvmStatic
|
||||||
|
@Suppress("unused")
|
||||||
|
fun loadingScriptConfigurationCallback() {
|
||||||
|
// this method is called from testData/script/definition/loading/async/template/template.kt
|
||||||
|
currentLoadingScriptConfigurationCallback?.invoke()
|
||||||
|
occurredLoadings++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setUp() {
|
||||||
|
super.setUp()
|
||||||
|
testScriptConfigurationNotification = true
|
||||||
|
|
||||||
|
addExtensionPointInTest(
|
||||||
|
DefaultScriptConfigurationManagerExtensions.LOADER,
|
||||||
|
project,
|
||||||
|
FileContentsDependentConfigurationLoader(project),
|
||||||
|
testRootDisposable
|
||||||
|
)
|
||||||
|
|
||||||
|
configureScriptFile("idea/testData/script/definition/loading/async/")
|
||||||
|
scriptConfigurationManager = ServiceManager.getService(project, ScriptConfigurationManager::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun tearDown() {
|
||||||
|
super.tearDown()
|
||||||
|
testScriptConfigurationNotification = false
|
||||||
|
occurredLoadings = 0
|
||||||
|
currentLoadingScriptConfigurationCallback = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadScriptConfigurationSynchronously(script: VirtualFile) {
|
||||||
|
// do nothings
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun assertAndDoAllBackgroundTasks() {
|
||||||
|
assertDoAllBackgroundTaskAndDoWhileLoading { }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun assertDoAllBackgroundTaskAndDoWhileLoading(actions: () -> Unit) {
|
||||||
|
assertTrue(doAllBackgroundTasksWith(actions))
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun doAllBackgroundTasksWith(actions: () -> Unit): Boolean {
|
||||||
|
return scriptConfigurationManager.testingBackgroundExecutor.doAllBackgroundTaskWith {
|
||||||
|
currentLoadingScriptConfigurationCallback = {
|
||||||
|
actions()
|
||||||
|
currentLoadingScriptConfigurationCallback = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun assertAppliedConfiguration(contents: String) {
|
||||||
|
val secondConfiguration = scriptConfigurationManager.getConfiguration(ktFile)!!
|
||||||
|
assertEquals(
|
||||||
|
contents,
|
||||||
|
secondConfiguration.defaultImports.single().let {
|
||||||
|
check(it.startsWith("x_"))
|
||||||
|
it.removePrefix("x_")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun makeChanges(contents: String) {
|
||||||
|
changeContents(contents)
|
||||||
|
|
||||||
|
scriptConfigurationManager.updater.ensureUpToDatedConfigurationSuggested(ktFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun changeContents(contents: String) {
|
||||||
|
runWriteAction {
|
||||||
|
val fileDocumentManager = FileDocumentManager.getInstance()
|
||||||
|
fileDocumentManager.reloadFiles(virtualFile)
|
||||||
|
val document = fileDocumentManager.getDocument(virtualFile)!!
|
||||||
|
document.setText(contents)
|
||||||
|
fileDocumentManager.saveDocument(document)
|
||||||
|
psiManager.reloadFromDisk(myFile)
|
||||||
|
myFile = psiManager.findFile(virtualFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun assertReports(expected: String) {
|
||||||
|
val actual = IdeScriptReportSink.getReports(virtualFile).single().message
|
||||||
|
assertEquals(expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun assertSuggestedConfiguration() {
|
||||||
|
assertTrue(virtualFile.hasSuggestedScriptConfiguration(project))
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun assertAndApplySuggestedConfiguration() {
|
||||||
|
assertTrue(virtualFile.applySuggestedScriptConfiguration(project))
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun assertNoSuggestedConfiguration() {
|
||||||
|
assertFalse(virtualFile.applySuggestedScriptConfiguration(project))
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun assertNoLoading() {
|
||||||
|
assertEquals(0, occurredLoadings)
|
||||||
|
occurredLoadings = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun assertSingleLoading() {
|
||||||
|
assertEquals(1, occurredLoadings)
|
||||||
|
occurredLoadings = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun assertAndLoadInitialConfiguration() {
|
||||||
|
assertNull(scriptConfigurationManager.getConfiguration(ktFile))
|
||||||
|
assertAndDoAllBackgroundTasks()
|
||||||
|
assertSingleLoading()
|
||||||
|
assertAppliedConfiguration("initial")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -265,6 +265,10 @@ abstract class AbstractScriptConfigurationTest : KotlinCompletionTestCase() {
|
|||||||
if (script == null) error("Test file with script couldn't be found in test project")
|
if (script == null) error("Test file with script couldn't be found in test project")
|
||||||
|
|
||||||
configureByExistingFile(script)
|
configureByExistingFile(script)
|
||||||
|
loadScriptConfigurationSynchronously(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun loadScriptConfigurationSynchronously(script: VirtualFile) {
|
||||||
updateScriptDependenciesSynchronously(myFile, project)
|
updateScriptDependenciesSynchronously(myFile, project)
|
||||||
|
|
||||||
VfsUtil.markDirtyAndRefresh(false, true, true, project.baseDir)
|
VfsUtil.markDirtyAndRefresh(false, true, true, project.baseDir)
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.script
|
||||||
|
|
||||||
|
class ScriptConfigurationLoadingTest : AbstractScriptConfigurationLoadingTest() {
|
||||||
|
fun testSimple() {
|
||||||
|
assertAndLoadInitialConfiguration()
|
||||||
|
|
||||||
|
makeChanges("A")
|
||||||
|
assertAndDoAllBackgroundTasks()
|
||||||
|
assertSingleLoading()
|
||||||
|
assertAndApplySuggestedConfiguration()
|
||||||
|
assertAppliedConfiguration("A")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testConcurrentLoadingWhileInQueue() {
|
||||||
|
assertAndLoadInitialConfiguration()
|
||||||
|
|
||||||
|
makeChanges("A")
|
||||||
|
makeChanges("B")
|
||||||
|
assertAndDoAllBackgroundTasks()
|
||||||
|
assertSingleLoading()
|
||||||
|
assertAndApplySuggestedConfiguration()
|
||||||
|
assertAppliedConfiguration("B")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testConcurrentLoadingWhileInQueueABA() {
|
||||||
|
assertAndLoadInitialConfiguration()
|
||||||
|
|
||||||
|
makeChanges("A")
|
||||||
|
makeChanges("B")
|
||||||
|
makeChanges("A")
|
||||||
|
assertAndDoAllBackgroundTasks()
|
||||||
|
assertSingleLoading()
|
||||||
|
assertAndApplySuggestedConfiguration()
|
||||||
|
assertAppliedConfiguration("A")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testConcurrentLoadingWhileInQueueABA2() {
|
||||||
|
assertAndLoadInitialConfiguration()
|
||||||
|
|
||||||
|
makeChanges("A")
|
||||||
|
makeChanges("initial")
|
||||||
|
assertAndDoAllBackgroundTasks()
|
||||||
|
assertNoLoading()
|
||||||
|
assertNoSuggestedConfiguration()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testConcurrentLoadingWhileAnotherLoadInProgress() {
|
||||||
|
assertAndLoadInitialConfiguration()
|
||||||
|
|
||||||
|
makeChanges("A")
|
||||||
|
assertDoAllBackgroundTaskAndDoWhileLoading {
|
||||||
|
makeChanges("B")
|
||||||
|
}
|
||||||
|
assertSingleLoading()
|
||||||
|
assertAndApplySuggestedConfiguration()
|
||||||
|
assertAppliedConfiguration("A")
|
||||||
|
|
||||||
|
assertAndDoAllBackgroundTasks()
|
||||||
|
assertSingleLoading()
|
||||||
|
assertAndApplySuggestedConfiguration()
|
||||||
|
assertAppliedConfiguration("B")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testConcurrentLoadingWhileAnotherLoadInProgressABA() {
|
||||||
|
assertAndLoadInitialConfiguration()
|
||||||
|
|
||||||
|
makeChanges("A")
|
||||||
|
assertDoAllBackgroundTaskAndDoWhileLoading {
|
||||||
|
makeChanges("B")
|
||||||
|
makeChanges("A")
|
||||||
|
}
|
||||||
|
assertSingleLoading()
|
||||||
|
assertAndApplySuggestedConfiguration()
|
||||||
|
assertAppliedConfiguration("A")
|
||||||
|
|
||||||
|
assertAndDoAllBackgroundTasks()
|
||||||
|
assertNoLoading()
|
||||||
|
assertNoSuggestedConfiguration()
|
||||||
|
assertAppliedConfiguration("A")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mutedTestConcurrentLoadingWhileAnotherLoadInProgressABA2() { // todo: fix and unmute
|
||||||
|
assertAndLoadInitialConfiguration()
|
||||||
|
|
||||||
|
makeChanges("A")
|
||||||
|
assertDoAllBackgroundTaskAndDoWhileLoading {
|
||||||
|
makeChanges("initial")
|
||||||
|
}
|
||||||
|
assertSingleLoading()
|
||||||
|
assertSuggestedConfiguration()
|
||||||
|
|
||||||
|
assertAndDoAllBackgroundTasks()
|
||||||
|
assertNoLoading()
|
||||||
|
assertNoSuggestedConfiguration()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testConcurrentLoadingWhileNotApplied() {
|
||||||
|
assertAndLoadInitialConfiguration()
|
||||||
|
|
||||||
|
makeChanges("A")
|
||||||
|
|
||||||
|
assertAndDoAllBackgroundTasks()
|
||||||
|
assertSingleLoading()
|
||||||
|
assertAppliedConfiguration("initial")
|
||||||
|
|
||||||
|
// we have loaded and not applied configuration for A
|
||||||
|
// let's invalidate file again and check that loading will occur
|
||||||
|
|
||||||
|
makeChanges("B")
|
||||||
|
assertAndDoAllBackgroundTasks()
|
||||||
|
assertSingleLoading()
|
||||||
|
assertAndApplySuggestedConfiguration()
|
||||||
|
assertAppliedConfiguration("B")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testConcurrentLoadingWhileNotAppliedABA() {
|
||||||
|
assertAndLoadInitialConfiguration()
|
||||||
|
|
||||||
|
makeChanges("A")
|
||||||
|
|
||||||
|
assertAndDoAllBackgroundTasks()
|
||||||
|
assertSingleLoading()
|
||||||
|
assertAppliedConfiguration("initial")
|
||||||
|
|
||||||
|
// we have loaded and not applied configuration for A
|
||||||
|
// let's invalidate file and change it back
|
||||||
|
// and check that loading will NOT occur
|
||||||
|
|
||||||
|
makeChanges("B")
|
||||||
|
makeChanges("A")
|
||||||
|
|
||||||
|
assertAndDoAllBackgroundTasks()
|
||||||
|
assertNoLoading()
|
||||||
|
assertAppliedConfiguration("initial")
|
||||||
|
|
||||||
|
assertAndApplySuggestedConfiguration()
|
||||||
|
assertAppliedConfiguration("A")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testConcurrentLoadingWhileNotAppliedABA2() {
|
||||||
|
assertAndLoadInitialConfiguration()
|
||||||
|
|
||||||
|
makeChanges("A")
|
||||||
|
assertAndDoAllBackgroundTasks()
|
||||||
|
assertSingleLoading()
|
||||||
|
assertSuggestedConfiguration()
|
||||||
|
|
||||||
|
makeChanges("initial")
|
||||||
|
assertAndDoAllBackgroundTasks()
|
||||||
|
assertNoLoading()
|
||||||
|
assertNoSuggestedConfiguration()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testLoadingForUsagesSearch() {
|
||||||
|
assertAndLoadInitialConfiguration()
|
||||||
|
|
||||||
|
changeContents("A")
|
||||||
|
assertNoLoading()
|
||||||
|
assertNoSuggestedConfiguration()
|
||||||
|
assertFalse(doAllBackgroundTasksWith {})
|
||||||
|
scriptConfigurationManager.updater.ensureConfigurationUpToDate(listOf(ktFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testReportsOnAutoApply() {
|
||||||
|
assertAndLoadInitialConfiguration()
|
||||||
|
assertReports("initial")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testReportsOnFailedConfiguration() {
|
||||||
|
assertAndLoadInitialConfiguration()
|
||||||
|
|
||||||
|
makeChanges("#BAD:A")
|
||||||
|
assertAndDoAllBackgroundTasks()
|
||||||
|
assertSingleLoading()
|
||||||
|
assertNoSuggestedConfiguration()
|
||||||
|
assertReports("#BAD:A")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testReportsOnSameConfiguration() {
|
||||||
|
assertAndLoadInitialConfiguration()
|
||||||
|
|
||||||
|
makeChanges("initial#IGNORE_IN_CONFIGURATION")
|
||||||
|
assertAndDoAllBackgroundTasks()
|
||||||
|
assertSingleLoading()
|
||||||
|
assertNoSuggestedConfiguration()
|
||||||
|
assertAppliedConfiguration("initial")
|
||||||
|
assertReports("initial#IGNORE_IN_CONFIGURATION")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testReportsAfterApply() {
|
||||||
|
assertAndLoadInitialConfiguration()
|
||||||
|
|
||||||
|
makeChanges("A")
|
||||||
|
assertAndDoAllBackgroundTasks()
|
||||||
|
assertSingleLoading()
|
||||||
|
assertAndApplySuggestedConfiguration()
|
||||||
|
assertAppliedConfiguration("A")
|
||||||
|
assertReports("A")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user